package com.lvy.hczn.front.business.service.impl;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.lvy.hczn.front.business.common.CacheConstants;
import com.lvy.hczn.front.business.common.Constants;
import com.lvy.hczn.front.common.constant.RedisConstants;
import com.lvy.hczn.front.common.core.domain.BaseModel;
import com.lvy.hczn.front.common.core.domain.entity.SysDictData;
import com.lvy.hczn.front.common.exception.UtilException;
import com.lvy.hczn.front.common.utils.DictUtils;
import com.lvy.hczn.front.system.domain.business.*;
import com.lvy.hczn.front.business.dto.MessageInfo;
import com.lvy.hczn.front.business.dto.OutboundOrderDetailDto;
import com.lvy.hczn.front.business.dto.Param;
import com.lvy.hczn.front.business.dto.StorageDone;
import com.lvy.hczn.front.business.service.*;
import com.lvy.hczn.front.common.core.domain.AjaxResult;
import com.lvy.hczn.front.common.core.redis.RedisCache;
import com.lvy.hczn.front.system.service.*;
import com.lvy.hczn.mq.rocket.ParamInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * mqtt业务逻辑处理
 *
 * @author yw
 * @date 2022-01-18 20:50:45
 **/
@Service
@Slf4j
public class MqttLogicServiceImpl implements MqttLogicService {

    @Autowired
    private ModelService modelService;

    @Autowired
    private MqttService mqttService;

    public static int num = 0;

    @Autowired
    private RandNumberService randNumberService;

    @Autowired
    private WcsService wcsService;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private SocketIOServer socketIOServer;

    @Autowired
    private UpOrDownPolicyService upOrDownPolicyService;

    @Autowired
    private UpOrDownPolicyHistoryService upOrDownPolicyHistoryService;

    @Autowired
    private AlarmRecordService alarmRecordService;

    @Autowired
    private ShelfLocationService shelfLocationService;

    @Autowired
    private GoodsBoxService goodsBoxService;

    @Autowired
    private GoodsBoxDetailService goodsBoxDetailService;

    @Autowired
    private AxisDataService axisDataService;

    @Autowired
    private OutboundOrderDetailService outboundOrderDetailService;

    @Autowired
    private DeliveryOrderService deliveryOrderService;

    @Autowired
    private PurchaseOrderDetailService purchaseOrderDetailService;

    @Autowired
    private WarehousingEntryService warehousingEntryService;

    @Autowired
    private SortTaskService sortTaskService;

    @Autowired
    private MqttRecordService mqttRecordService;

    @Autowired
    private SpuService spuService;

    @Autowired
    private SkuService skuService;

    @Autowired
    private AccessManagerService accessManagerService;

    @Autowired
    private TemperatureZoneService temperatureZoneService;

    @Autowired
    private WarehouseBoxEntryService warehouseBoxEntryService;

    @Autowired
    private GoodsBoxSpecService goodsBoxSpecService;

    @Autowired
    private ISysOperLogService sysOperLogService;

    @Autowired
    private ElectronicWeightService electronicWeightService;

    @Autowired
    private DeliveryOrderDetailService deliveryOrderDetailService;

    @Autowired
    private PurchaseOrderService purchaseOrderService;

    /**
     * 根据topic区分业务处理
     *
     * @param topic 主题
     * @param msg   内容
     * @return
     * @author yw
     * @date 2022-01-18 20:49:39
     */
    @Override
    public void doBusiness(String topic, String msg) throws Exception {
        switch (topic) {
            case "topic_default":
                /**
                 * 测试
                 */
                log.error("topic_default接收内容：{}", msg);
                /*WcsResponse response = JSONUtil.toBean(msg, WcsResponse.class);
                UpOrDownPolicy upOrDownPolicy = upOrDownPolicyService.getOne(Wrappers.<UpOrDownPolicy>lambdaQuery().eq(UpOrDownPolicy::getBoxCode, response.getBoxCode()));
                if (upOrDownPolicy != null) {
                    log.info("topic_default redis key:" + CacheConstants.REDIS_KEY_SOCKET_CLIENT + ":" + upOrDownPolicy.getClientId());
                    Object object = redisCache.get(CacheConstants.REDIS_KEY_SOCKET_CLIENT + ":" + upOrDownPolicy.getClientId());
                    MessageInfo messageInfo = new MessageInfo();
                    messageInfo.setData(response);
                    socketIOServer.getClient(UUID.fromString(String.valueOf(object))).sendEvent("topicDefault", JSONUtil.toJsonStr(messageInfo));
                }*/
                break;
            //wcs返回响应
            case "WCS_TO_WMS":
                //todo wcs响应之后，再发第二个指令到wcs
                //todo redis存储true or false，一秒超时，如下发指令响应及称重响应，需校验是否响应再发下个指令，web指令发送前做个校验
                //todo 指令地址做一个异常抛出
                // 下发指令后，wcs返回响应
                //redisCache.set(CacheConstants.WCS_RESPONSE + "wms",true,1);
                log.error("主题" + topic);
                log.error("内容" + msg);
                //wms未启动，wcs一直重发情况，待测试（心跳断开，wcs不应发，最多个别数据有问题，待观察）
                /*synchronized (this){
                    redisCache.set(CacheConstants.WCS_RESPONSE + "wms", true);
                    wcsToWmsResponse("wcsToWmsResponse", msg, CacheConstants.REDIS_KEY_SOCKET_WCS_TO_WMS, false);
                }*/
                redisCache.set(CacheConstants.WCS_RESPONSE + "wms", true);
                wcsToWmsResponse("wcsToWmsResponse", msg, CacheConstants.REDIS_KEY_SOCKET_WCS_TO_WMS, false);
                break;
            case "TakeBox_Done":
                log.error("货箱已离开库位" + topic);
                log.error("货箱已离开库位" + msg);
                //wms未启动，wcs一直重发情况，待测试（心跳断开，wcs不应发，最多个别数据有问题，待观察）
                /*synchronized (this){
                    //货箱已离开库位
                    takeBoxDone(msg, topic);
                }*/
                //货箱已离开库位
                takeBoxDone(msg, topic);
                break;
            case "InBufferPos":
                log.error("出库到达缓存位" + topic);
                log.error("出库到达缓存位" + msg);
                //wms未启动，wcs一直重发情况，待测试（心跳断开，wcs不应发，最多个别数据有问题，待观察）
                /*synchronized (this){
                    //出库到达缓存位
                    inBufferPos("inBufferPos", msg, topic);
                }*/
                //出库到达缓存位
                inBufferPos("inBufferPos", msg, topic);
                break;
            case "LeaveBufferPos":
                log.error("货箱入库离开缓存位：" + msg);
                //wms未启动，wcs一直重发情况，待测试（心跳断开，wcs不应发，最多个别数据有问题，待观察）
                /*synchronized (this){
                    leaveBufferPos("leaveBufferPos", msg, "", false, topic);
                }*/
                leaveBufferPos("leaveBufferPos", msg, "", false, topic);
                break;
            case "Storage_Done":
                log.error("入库到位" + topic);
                log.error("入库到位" + msg);
                //wms未启动，wcs一直重发情况，待测试（心跳断开，wcs不应发，最多个别数据有问题，待观察）
                /*synchronized (this){
                    //入库到位
                    storageDone("", msg, topic);
                }*/
                //入库到位
                storageDone("", msg, topic);
            case "WD_WCS_TO_WMS":
                //wcs发送给wms的心跳检测信号,存储到redis,发送交互指令的时候，get一下，要是为空，则表示wcs未返回信号，则wcs断开
                //wms不再向mqtt发送信号，目的是提示wms操作人员与仓库断开，不能操作
                //todo key加仓库编号
                redisCache.set(CacheConstants.REDIS_KEY_WCS_SIGNAL, true, 1);
                mqttService.sendToMqtt(Constants.TOPIC_MQTT_WMS_WCS_WDSignal, msg);
                break;
            case "Alarm_WCS_TO_WMS":
                //报警记录
                Map<String, Object> map = JSONUtil.toBean(msg, HashMap.class);
                if (map != null) {
                    AlarmRecord alarmRecord = new AlarmRecord();
                    alarmRecord.setType(Constants.ALARM_RECORD_ONE);
                    alarmRecord.setCode(String.valueOf(map.get("AlarmActive")));
                    alarmRecordService.save(alarmRecord);
                }
                break;
            case "AxisParament_TO_WMS":
                axisData(msg, topic);
                break;
            case "Thermometer":
                thermometer(msg, topic);
                break;
            case "Recover_TO_WMS":
                log.error("数据恢复" + topic);
                log.error("数据恢复" + msg);
                recover(msg, topic);
                break;
            case "All_TO_WMS":
                extend(msg, topic);
                break;
            case "MQ3_Weight_TO_WMS":
                log.error("分拣称重" + topic);
                log.error("分拣称重" + msg);
                //redisCache.set(CacheConstants.WCS_RESPONSE + "weight",true,1);
                Integer num = redisCache.getCacheObject(CacheConstants.WCS_RESPONSE + "weight:out");
                if (num != null) {
                    redisCache.set(CacheConstants.WCS_RESPONSE + "weight", true, num);
                } else {
                    redisCache.set(CacheConstants.WCS_RESPONSE + "weight", true, 10);
                }

                sortWeightResponse("sortWeight", msg, "MQ3_Weight_TO_WMS", true);
                break;
            case "WCS_Door_IsOpen":
                //WCS开门通知WMS
                accessManagerCode(msg);
                break;
            case "SetEmptySeat_To_WMS":
                //叠箱发送百分比后，wcs响应
                //msg格式：叠箱wcs响应内容{"iData":22}
                log.error("叠箱wcs响应内容" + msg);
                stackBoxesPercent(msg, topic);
                break;
            case "access_manager_code":
                //WCS反馈的门禁扫码内容，根据内容判断是否开门
                log.error("门禁内容：{}", msg);
                //todo 门禁,access_manager_code待wcs定义，msg内容待wcs定义
                Map<String, String> mapInfo = JSON.parseObject(msg, Map.class);
                String key = "accessManagerCode";
                String content = "";
                if (mapInfo != null && mapInfo.containsKey(key)) {
                    content = mapInfo.get(key);
                }
                accessManagerService.openTheDoor(content, false);
                break;
            case "electronic_weight":
                //电子秤散装/分拣/报损称重topic返回
                electronicWeightService.electronicWeightInfo("electronicWeight", msg, topic);
                break;
            default:
                //todo 后期考虑wcs返回不满足出入库条件的逻辑抛出的异常，wms的数据回滚
                System.out.println();
        }
    }

    /**
     * 仓库百分比设置响应
     *
     * @param msg
     * @param topic
     * @return void
     * @author yw
     * @date 2023-10-21 21:09:17
     */
    public void stackBoxesPercent(String msg, String topic) {
        log.error("stackBoxesPercent:仓库百分比设置响应:" + msg);
        Object object = redisCache.get(CacheConstants.WCS_RESPONSE + ":percent");
        if (object != null) {
            MqttRecord mqttRecord = JSON.parseObject(String.valueOf(object), MqttRecord.class);
            if (!StrUtil.isEmpty(mqttRecord.getId())) {
                mqttRecord.setDone(true);
                if (mqttRecordService.updateById(mqttRecord)) {
                    redisCache.del(CacheConstants.WCS_RESPONSE + ":percent");
                }
            } else {
                log.error("stackBoxesPercent缓存内容ID为空:" + JSON.toJSONString(mqttRecord));
            }
        } else {
            log.error("stackBoxesPercent缓存内容为空");
        }
    }

    /**
     * 门禁扫描，扫描后，需处理业务逻辑，、
     * 如：根据类型只扫描一次，或者多久失效，或者永久失效，以及门禁打开后，推送到平台，告知到达仓库
     *
     * @param msg
     * @return void
     * @author yw
     * @date 2023-07-14 14:28:59
     */
    public void accessManagerCode(String msg) {
        //todo 门禁
        //wcs识别扫码信息返回wms，wms校验，符合则发送开门指令到wcs
        //Door01_IsOpen=false：门打开，true：门关闭
//        log.error("accessManagerCode:{}", msg);
        Map<String, Boolean> mapInfo = JSON.parseObject(msg, Map.class);
        if (mapInfo != null && mapInfo.get("Door01_IsOpen") != null && !mapInfo.get("Door01_IsOpen")) {
            Object object = redisCache.get(RedisConstants.ACCESS_MANAGER_CODE);
            if (object != null) {
                String accessCode = String.valueOf(object);
                AccessManager accessManager = accessManagerService.getOne(Wrappers.<AccessManager>lambdaQuery().eq(AccessManager::getAccessCode, accessCode).orderByDesc(BaseModel::getCreateTime).last("limit 1"));
                if (accessManager != null) {
                    if (accessManager.getCountScan() == 0) {
                        accessManager.setScanTime(LocalDateTime.now());
                    }
                    log.info("accessManagerCode:{}", JSON.toJSONString(object));
                    accessManager.setCountScan(accessManager.getCountScan() + 1);
                    if (accessManagerService.updateById(accessManager)) {
                        redisCache.del(RedisConstants.ACCESS_MANAGER_CODE);
                        accessManagerService.accessManagerCode(accessManager, true);
                    }
                }
            }
        }
//                log.error("WCS_Door01_IsOpen info:{}",msg);
    }

    /**
     * 分拣中称重响应
     *
     * @param event
     * @param msg
     * @param topic
     * @param isClear
     * @return void
     * @author yw
     * @date 2022-07-09 16:19:27
     */
    public void sortWeightResponse(String event, String msg, String topic, boolean isClear) throws Exception {
        WcsResponse response = JSONUtil.toBean(msg, WcsResponse.class);
        UpOrDownPolicy upOrDownPolicy;
        upOrDownPolicy = upOrDownPolicyService.getOne(Wrappers.<UpOrDownPolicy>lambdaQuery().eq(UpOrDownPolicy::getBoxCode, response.getBoxCode()).eq(UpOrDownPolicy::getType, Constants.BOX_OPERATION_WEIGHT));
        log.error("分拣中称重响应：" + JSONUtil.toJsonStr(response));
        if (upOrDownPolicy == null) {
            UpOrDownPolicy object = (UpOrDownPolicy) redisCache.get(Constants.BOX_OPERATION_WEIGHT + ":" + response.getBoxCode());
            if (object != null) {
                upOrDownPolicy = object;
            }
        }
        if (upOrDownPolicy != null) {
            MessageInfo messageInfo = new MessageInfo();
            GoodsBox goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, upOrDownPolicy.getBoxCode()));
            if (goodsBox != null) {
                List<GoodsBoxDetail> detailList = goodsBoxDetailService.list(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, goodsBox.getBoxCode()).gt(GoodsBoxDetail::getWeight, BigDecimal.ZERO));

                if (upOrDownPolicy.getProcessType() == Constants.STATE_PROCESS_TYPE_SEVEN) {
                    messageInfo.setWarehousingEntryId(upOrDownPolicy.getWarehousingEntryId());
                    if (Constants.ANSWER_ID_OK == response.getAnswerID()) {
                        //采购入库称重
                        warehousingEntrySortWeight(upOrDownPolicy, response, messageInfo, goodsBox, detailList);
                    }
                } else {
                    if (Constants.ANSWER_ID_OK == response.getAnswerID()) {
                        //当总重量小于货箱重量（误差），则认为时空箱，总重量为0
                        BigDecimal weight = response.getTotalWeight().subtract(goodsBox.getBoxStandardWeight());
                        if (weight.compareTo(BigDecimal.ZERO) > 0) {
                            messageInfo.setTotalWeight(weight.divide(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT)).setScale(2, RoundingMode.HALF_UP));
                        } else {
                            messageInfo.setTotalWeight(BigDecimal.ZERO);
                        }
                        messageInfo.setData(response);

                        //分拣后货箱剩余总重量
                        BigDecimal totalWeight = goodsBox.getWeight().multiply(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT)).add(goodsBox.getBoxStandardWeight());

                        //实际称重重量
                        BigDecimal weigh = response.getTotalWeight();
                        //分拣后的货箱逻辑重量与实际称重重量的误差值
                        BigDecimal reweigh = totalWeight.subtract(weigh);
                        messageInfo.setTotalWeight(weigh);
                        //实际分拣重量,调整为KG
                        if (reweigh.compareTo(BigDecimal.ZERO) < 0) {
                            messageInfo.setSortWeight(totalWeight.divide(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT)).setScale(2, RoundingMode.HALF_UP));
                        } else {
                            messageInfo.setSortWeight(reweigh.divide(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT)).setScale(2, RoundingMode.HALF_UP));
                        }

                        //货箱误差+产品误差
                        BigDecimal errMin = goodsBox.getErrorMin();
                        BigDecimal errMax = goodsBox.getErrorMax();
                        Spu spu = spuService.getById(upOrDownPolicy.getSpuId());
                        if (spu != null) {
                            if (spu.getErrorMin() != null) {
                                errMin = errMin.add(spu.getErrorMin());
                            }
                            if (spu.getErrorMax() != null) {
                                errMax = errMax.add(spu.getErrorMax());
                            }
                        }

                        if (response.getSeatID() != null && !response.getSeatID().isEmpty()) {
                            messageInfo.setSeatId(response.getSeatID().get(0));
                        }

                        //重量比若是不在误差范围内，则返回异常
                        //1.在误差范围内，正常称重分拣
                        //2.不在误差范围内，则考虑是否标品
                        //3.标品则不超过两份重量，则认为正常，如：每份一斤，而实际称重在两斤一下，则认为只拿一份称重，则认为正常
                        //4.非标品则提示称重不符
                        reweigh = reweigh.subtract(upOrDownPolicy.getSortWeight().multiply(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT)));
                        if (reweigh.compareTo(errMin.negate()) >= 0 && reweigh.compareTo(errMax) <= 0) {
                            //do something...
                        } else {
                            //分拣称重异常，非wcs返回，wms自定义
                            response.setAnswerID(Constants.ANSWER_ID_ERROR);
                            response.setErrorID(Constants.WMS_SORT_WEIGHT_ERROR);
                            if (detailList != null && !detailList.isEmpty()) {
                                /**校验是否标品**/
                                Map<String, List<GoodsBoxDetail>> detailMap = detailList.stream().collect(Collectors.groupingBy(it -> it.getSpuId()));
                                if (detailMap.containsKey(upOrDownPolicy.getSpuId())) {
                                    List<GoodsBoxDetail> boxDetailList = detailMap.get(upOrDownPolicy.getSpuId());
                                    BigDecimal bigDecimal = BigDecimal.ZERO;
                                    for (GoodsBoxDetail detail : boxDetailList) {
                                        if (detail.getSpuId().equals(upOrDownPolicy.getSpuId()) && detail.getSkuCode().equals(upOrDownPolicy.getSkuCode())) {
                                            //标品则不超过两份重量，则认为正常
                                            if (!detail.isInBulk()) {
                                                bigDecimal.add(upOrDownPolicy.getSortWeight().multiply(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT)).subtract(errMin));
                                                break;
                                            }
                                        }
                                    }

                                    if (reweigh.abs().compareTo(bigDecimal) >= 0) {
                                        response.setAnswerID(Constants.ANSWER_ID_ERROR);
                                        //分拣称重异常，非wcs返回，wms自定义
                                        response.setErrorID(Constants.WMS_SORT_WEIGHT_ERROR);
                                    } else {
                                        response.setAnswerID(Constants.ANSWER_ID_OK);
                                        response.setErrorID(null);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            if (Constants.ANSWER_ID_OK == response.getAnswerID()) {
                //todo 请求多次会自动+1，需过滤，如出库响应，按多次按钮，则会一直+1，而变成其他流程以及与mqtt断开重连的时候，会有重复记录，也会+1
                //todo 不过方法为自动响应，无需人为操作，随已做了mqtt重复过滤，待观察再决定是否优化
                //todo 出库响应，夹起货箱，会返回行列，确定这个货位为空，wms做更新处理
                responseWeb(event, upOrDownPolicy, messageInfo, isClear, false, null);
            } else {
                log.error("分拣称重异常：" + JSONUtil.toJsonStr(upOrDownPolicy));
                initMessageInfo(upOrDownPolicy, Constants.FAIL, messageInfo);
                //wcs反馈异常，逻辑处理
                dealWithError(event, response, upOrDownPolicy, messageInfo);
            }
            mqttRecordService.initMqttRecord(response.getBoxCode(), topic, 0, msg);
        }
    }

    /**
     * 采购入库称重
     *
     * @param upOrDownPolicy
     * @param response
     * @param messageInfo
     * @return void
     * @author yw
     * @date 2022-08-31 16:49:05
     */
    public void warehousingEntrySortWeight(UpOrDownPolicy upOrDownPolicy, WcsResponse response, MessageInfo messageInfo, GoodsBox goodsBox, List<GoodsBoxDetail> detailList) throws Exception {
        if (!StrUtil.isEmpty(upOrDownPolicy.getWarehousingEntryId())) {
            //采购入库的货箱，只有净重量
            BigDecimal totalWeight = goodsBox.getBoxStandardWeight();
            String redisKey = CacheConstants.WAREHOUSING_ENTRY_WEIGHT + upOrDownPolicy.getBoxCode() + ":" + upOrDownPolicy.getClientId();
            Map<Object, Object> map = redisCache.getHash(redisKey);
            if (map != null && !map.isEmpty()) {
                for (Map.Entry<Object, Object> entry : map.entrySet()) {
                    if (!String.valueOf(entry.getKey()).equals(upOrDownPolicy.getWarehousingEntryId())) {
                        totalWeight = totalWeight.add(new BigDecimal(String.valueOf(entry.getValue())));
                    }
                }
            }
            //当前入库单重量=当前货箱已称重过的入库单总重量-货箱总总量的，暂时不减误差值，测试或试运营看情况
            BigDecimal weight = response.getTotalWeight().subtract(totalWeight);
            if (weight.compareTo(BigDecimal.ZERO) > 0 && weight.compareTo(goodsBox.getErrorMax()) > 0) {
                redisCache.setHashExits(redisKey, upOrDownPolicy.getWarehousingEntryId(), weight, 3, TimeUnit.DAYS);
                messageInfo.setTotalWeight(weight.divide(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT)).setScale(2, RoundingMode.HALF_UP));
            } else {
                response.setAnswerID(Constants.ANSWER_ID_ERROR);
                response.setErrorID(Constants.WMS_PURCHASE_WEIGHT_ERROR);
                messageInfo.setTotalWeight(BigDecimal.ZERO);
            }
        }
    }

    /**
     * 响应信息初始化
     */
    public void initMessageInfo(UpOrDownPolicy upOrDownPolicy, Integer code, MessageInfo messageInfo) {
        messageInfo.setCode(code);
        upOrDownPolicy.setProcessState(upOrDownPolicy.getProcessState() + 1);
        messageInfo.setProcessState(upOrDownPolicy.getProcessState());
        messageInfo.setBoxCode(upOrDownPolicy.getBoxCode());
        messageInfo.setProcessType(upOrDownPolicy.getProcessType());
        messageInfo.setClientId(upOrDownPolicy.getClientId());
    }

    /**
     * 货箱已离开库位
     *
     * @param msg
     * @param topic
     * @return void
     * @author yw
     * @date 2022-04-02 21:24:42
     */
    @Transactional(rollbackFor = Exception.class)
    public void takeBoxDone(String msg, String topic) throws Exception {
        //todo 查询与修改,后期考虑优化，放入redis之类
        StorageDone response = JSONUtil.toBean(msg, StorageDone.class);

        UpOrDownPolicy upOrDownPolicy = upOrDownPolicyService.getOne(Wrappers.<UpOrDownPolicy>lambdaQuery().eq(UpOrDownPolicy::getBoxCode, response.getBoxCode()).eq(UpOrDownPolicy::getType, Constants.BOX_OPERATION_DOWN));
        //批量出空箱，此处未返回takeNum，upOrDownPolicy永远为NULL

        /**置空库位**/
        LambdaUpdateWrapper<ShelfLocation> shelfLocationWrapper = new LambdaUpdateWrapper<>();
        shelfLocationWrapper.eq(ShelfLocation::getBoxCode, response.getBoxCode()).eq(ShelfLocation::getSeatID, response.getSeatID()).eq(ShelfLocation::getLineID, response.getLineID()).eq(ShelfLocation::getListID, response.getListID()).set(ShelfLocation::getBoxCode, null).set(ShelfLocation::getState, response.getState()).set(ShelfLocation::getTempNum, response.getTempNum());
        shelfLocationService.update(shelfLocationWrapper);

        /**置空货箱的库位**/
        GoodsBox goodsBox = new GoodsBox();
        LambdaUpdateWrapper<GoodsBox> goodsBoxWrapper = new LambdaUpdateWrapper<>();
        goodsBoxWrapper.eq(GoodsBox::getBoxCode, response.getBoxCode());
        goodsBox.setWarehouseNumber(0);
        goodsBox.setWarehouseRow(0);
        goodsBox.setWarehouseColumn(0);
//            goodsBox.setTemperatureZoneCode(0);
//            goodsBox.setActTemperatureCode(0);
        goodsBox.setOutboundTime(LocalDateTime.now());
        //取货单出库夹取货箱的时候，更新状态为出库中
        goodsBox.setStatus(Constants.GOODS_BOX_STATE_ONE);
        goodsBoxService.update(goodsBox, goodsBoxWrapper);

        int state = 0;
        //wcs自己夹取货箱挪库位，则没有上下架指令
        if (upOrDownPolicy != null) {
            //批量出库出库单没有货箱编号，货箱已离开库位未返回takeNum，无法更新出库单，只能等到到达缓存位再更新出库单
            MessageInfo messageInfo = new MessageInfo();
            //仓库编号临时存储
            redisCache.set(CacheConstants.SORT_TASK_SEAT_ID + upOrDownPolicy.getClientId() + upOrDownPolicy.getBoxCode(), response.getSeatID(), 3, TimeUnit.DAYS);
//            initMessageInfo(upOrDownPolicy, MqConstants.SUCCESS, messageInfo);
            messageInfo.setSeatId(response.getSeatID());
            responseWeb("", upOrDownPolicy, messageInfo, false, false, goodsBox);

//            redisCache.del(CacheConstants.SORT_QUEUE_FULLED);
            //夹取箱子时释放，wcs释放队列，然后再入库
            ThreadUtil.execAsync(() -> {
                sortingOutBatchInfo(upOrDownPolicy, topic, null, false);
            });
            state = upOrDownPolicy.getDataState();
        }

        mqttRecordService.initMqttRecord(response.getBoxCode(), topic, state, msg);

        /**响应wcs信息**/
        WcsResponse wcsResponse = new WcsResponse();
        wcsResponse.setAnswerID(Constants.ANSWER_ID_OK);
        wcsResponse.setBoxCode(response.getBoxCode());

        wcsService.sendDelay();

        String json = JSONUtil.toJsonStr(wcsResponse);
        mqttService.sendToMqtt(Constants.TOPIC_MQTT_TAKE_BOX_ANSWER, json);
        mqttRecordService.initMqttRecord(response.getBoxCode(), Constants.TOPIC_MQTT_TAKE_BOX_ANSWER, state, json);
    }

    /**
     * 分拣继续下架
     *
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-07-02 11:10:00
     */
    public void sortingOutBatchInfo(UpOrDownPolicy upOrDownPolicy, String topic, Param param, boolean leaveInBuffer) {
        //校验，分拣连续发时，且触发货箱离开库位流程，货箱
        try {
            if (param == null) {
                param = new Param();
                param.setUpOrDownPolicy(upOrDownPolicy);
            }
            wcsService.sortingOutBatch(param, leaveInBuffer);
            //wcs分拣消息队列满的时候，不发
            /*Object isFulled = redisCache.get(CacheConstants.SORT_QUEUE_FULLED);
            if (isFulled == null) {
                wcsService.sortingOutBatch(param, leaveInBuffer);
            }*/
        } catch (Exception e) {
            log.error("离开库位时，发送分拣货箱失败:{},{},{}", topic, e.getMessage(), JSONUtil.toJsonStr(upOrDownPolicy));
        }
    }

    /**
     * 出库到达缓存位响应,wms做响应处理
     * 1：返回数据列表到前端
     * 2：UpOrDownPolicy数据物理删除，存入到UpOrDownPolicyHistory
     * 3：返回响应到wcs
     *
     * @param msg
     * @param topic
     * @return void
     * @author yw
     * @date 2022-04-02 22:28:17
     */
    @Transactional(rollbackFor = Exception.class)
    public void inBufferPos(String event, String msg, String topic) throws Exception {
        WcsData data = JSONUtil.toBean(msg, WcsData.class);
        UpOrDownPolicy upOrDownPolicy = upOrDownPolicyService.getOne(Wrappers.<UpOrDownPolicy>lambdaQuery().eq(UpOrDownPolicy::getBoxCode, data.getBoxCode()).eq(UpOrDownPolicy::getType, Constants.BOX_OPERATION_DOWN));
        Param param = new Param();
        GoodsBox goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, data.getBoxCode()));
        if (upOrDownPolicy != null) {
            if (goodsBox != null) {
                TemperatureZone temperatureZone = temperatureZoneService.getOne(Wrappers.<TemperatureZone>lambdaQuery().eq(TemperatureZone::getTemperatureZoneCode, String.valueOf(goodsBox.getActTemperatureCode())));
                if (temperatureZone != null) {
                    goodsBox.setTemperatureName(temperatureZone.getName());
                }
//                List<GoodsBoxDetail> list = goodsBoxDetailService.list(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, goodsBox.getBoxCode()));
                param.setGoodsBox(goodsBox);
//                param.setList(list);
                /**返回web**/
                MessageInfo messageInfo = new MessageInfo();
                Object object = redisCache.get(CacheConstants.SORT_TASK_SEAT_ID + upOrDownPolicy.getClientId() + upOrDownPolicy.getBoxCode());
                if (object != null) {
                    messageInfo.setSeatId((Integer) object);
                    upOrDownPolicy.setClientId(String.valueOf(messageInfo.getSeatId()));
                }
                messageInfo.setData(param);

                //忘了为啥要加了，再说吧
                GoodsBox box = new GoodsBox();

                //出库且不入库，且到达缓存位
                if (upOrDownPolicy.getProcessType().equals(Constants.STATE_PROCESS_TYPE_ZERO)) {
                    goodsBox.setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
                    box.setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
                    goodsBoxDetailService.delPhysicalByBoxCode(goodsBox.getBoxCode());

                    //删除采购入库时的缓存
                    String boxKey = CacheConstants.WCS_RESPONSE + upOrDownPolicy.getBoxCode() + ":" + Constants.STATE_PROCESS_TYPE_THREE;
                    redisCache.del(boxKey);

                    log.error("批量空箱出库：{}", msg);
                    //空箱或指定货箱出库推送到平台库位调整
                    UpOrDownPolicy up = upOrDownPolicy;
                    ThreadUtil.execAsync(() -> downloadEmptyBox(up));
                }

                goodsBox.setStatus(Constants.GOODS_BOX_STATE_TWO);
                box.setStatus(Constants.GOODS_BOX_STATE_TWO);

                log.error("goodsBoxService.updateById3");
                box.setId(goodsBox.getId());
                goodsBoxService.updateById(box);
                responseWeb(event, upOrDownPolicy, messageInfo, true, true, goodsBox);

                mqttRecordService.initMqttRecord(data.getBoxCode(), topic, upOrDownPolicy.getDataState(), msg);
                /*if (upOrDownPolicy.getProcessType().equals(MqConstants.STATE_PROCESS_TYPE_ONE)) {
                    inBufferUpOrDownPolicyToWcs(upOrDownPolicy, goodsBox);
                }*/
            }
        } else {
            //目前wcs会响应wms多次，1034为分拣，暂时过滤分拣响应两次不处理
            if (data.getState() != 1034) {
                //取一定数量的货箱，即一个指令输入数量传入wcs即可，outboundOrderCode即为取货单，一个取货单包含多个货箱，目前wcs设置队列可容40个取货单
                //指定数量取货箱，没有货箱编号，只有货箱到达缓存位，wcs才会响应取货批次号，以及货箱编号，此指令的货箱编号是随机生成
                //到位之后，删除指令，一次性下，只有一条指令
                //批量出库，目前时生成一条upOrDownPolicy指令，响应后删除，当upOrDownPolicy == null进入else，默认为
                //批量出库，upOrDownPolicy非批量出库且进入else，则upOrDownPolicy为异常数据，会有问题，出现时再看是场景还是bug
                //解决方案：批量出库时，可生成相同outboundOrderCode的多条指令，wcs会返回可出库多少箱子，upOrDownPolicy根据outboundOrderCode需删除多余的指令
                log.error("WcsData:" + JSONUtil.toJsonStr(data));
                //批量出库，生成多条出库指令
                upOrDownPolicy = upOrDownPolicyService.getOne(Wrappers.<UpOrDownPolicy>lambdaQuery().eq(UpOrDownPolicy::getOutboundOrderCode, data.getTakeNum()).last("limit 1"));
                if (upOrDownPolicy == null) {
                    upOrDownPolicy = new UpOrDownPolicy();
                    upOrDownPolicy.setBoxCode(data.getBoxCode());
                    upOrDownPolicy.setOutboundOrderCode(data.getTakeNum());
                    upOrDownPolicy.setProcessState(Constants.STATE_TO_WCS_SEVEN);
                    upOrDownPolicy.setProcessType(Constants.STATE_PROCESS_TYPE_ZERO);
                    upOrDownPolicy.setType(Constants.BOX_OPERATION_DOWN);
                    upOrDownPolicy.setDataState(data.getState());
                    Object obj = redisCache.get("outbound:clientId:" + data.getTakeNum());
                    if (obj != null) {
                        upOrDownPolicy.setClientId(String.valueOf(obj));
                    }
                }
                redisCache.set("outbound:clientId:" + data.getTakeNum(), upOrDownPolicy.getClientId(), 3, TimeUnit.DAYS);
                if (goodsBox != null && !StrUtil.isEmpty(upOrDownPolicy.getClientId())) {
                    /**批量出空箱，货箱离开库位的响应upOrDownPolicy为空，在此处重置货位，库位**/
                    goodsBox.setWarehouseNumber(0);
                    goodsBox.setWarehouseRow(0);
                    goodsBox.setWarehouseColumn(0);
//                    goodsBox.setTemperatureZoneCode(0);
//                    goodsBox.setActTemperatureCode(0);
                    goodsBox.setOutboundTime(LocalDateTime.now());

                    /**置空库位**/
                    LambdaUpdateWrapper<ShelfLocation> shelfLocationWrapper = new LambdaUpdateWrapper<>();
                    shelfLocationWrapper.eq(ShelfLocation::getBoxCode, data.getBoxCode()).set(ShelfLocation::getBoxCode, "");
                    shelfLocationService.update(shelfLocationWrapper);

                    param.setGoodsBox(goodsBox);

                    //更新货箱状态为已出库空箱，当只有货箱编号，没箱子，且要入库，只有wcs称重校验，不可入
                    goodsBox.setStatus(Constants.GOODS_BOX_STATE_TWO);

                    //客户取货出库，或者空箱出库,upOrDownPolicy为null进入else，则要么批量出库，要么指令有异常删掉了记录，
                    //此处做货箱出库且不入库指令的物理删除，若是空箱，一般是出库作为新原料箱入库，若是原料箱出库，一般是仓库调度
                    if (upOrDownPolicy.getProcessType() == Constants.STATE_PROCESS_TYPE_ZERO) {
                        goodsBox.setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
                        goodsBoxDetailService.delPhysicalByBoxCode(data.getBoxCode());

                        //删除采购入库时的缓存
                        String boxKey = CacheConstants.WCS_RESPONSE + upOrDownPolicy.getBoxCode() + ":" + Constants.STATE_PROCESS_TYPE_THREE;
                        redisCache.del(boxKey);

                        log.error("批量空箱出库1：{}", msg);
                        upOrDownPolicy.setBoxCode(data.getBoxCode());
                        //空箱或指定货箱出库推送到平台库位调整
                        UpOrDownPolicy up = upOrDownPolicy;
                        ThreadUtil.execAsync(() -> downloadEmptyBox(up));
                    }

                    /**返回web**/
                    MessageInfo messageInfo = new MessageInfo();
                    messageInfo.setData(param);
                    responseWeb("", upOrDownPolicy, messageInfo, true, true, goodsBox);
                }
            }
        }

        //空箱从货架下架，推送到平台
        ThreadUtil.execAsync(() -> {
            syncBoxEmpty(goodsBox, data);
        });

        /**响应wcs**/
        WcsResponse wcsResponse = new WcsResponse();
        wcsResponse.setAnswerID(Constants.ANSWER_ID_OK);
        wcsResponse.setBoxCode(data.getBoxCode());
        wcsService.sendDelay();

        String json = JSONUtil.toJsonStr(wcsResponse);
        mqttService.sendToMqtt(Constants.TOPIC_MQTT_IN_BUFFER_POS_ANSWER, json);
        mqttRecordService.initMqttRecord(data.getBoxCode(), Constants.TOPIC_MQTT_IN_BUFFER_POS_ANSWER, 0, json);
    }

    /**
     * 空箱从货架下架，推送到平台
     *
     * @param goodsBox
     * @param data
     * @return void
     * @author yw
     * @date 2023-11-25 16:58:57
     */
    public void syncBoxEmpty(GoodsBox goodsBox, WcsData data) {
        //260：出库且不入库指令，wcs响应的指令为260，默认皆为空箱出库
        if (data.getState() == 260 || data.getState() == 258) {
            WarehouseBoxEntry warehouseBoxEntry = new WarehouseBoxEntry();
            Integer serviceType = com.lvy.hczn.front.common.constant.Constants.BOX_EMPTY_SERVICE_TYPE_IN;
            Integer overType = com.lvy.hczn.front.common.constant.Constants.BOX_EMPTY_OVER_TYPE_ZERO;
            warehouseBoxEntryService.initWarehouseBoxEntryByGoodsBox(goodsBox, warehouseBoxEntry, serviceType, overType, "");
            warehouseBoxEntryService.syncToDataTransmit(warehouseBoxEntry);
        }
    }

    /**
     * 空箱或指定货箱出库推送到平台
     *
     * @param upOrDownPolicy
     * @return com.lvy.hczn.common.core.util.ResultUtil
     * @author yw
     * @date 2022-07-29 15:52:12
     */
    public synchronized void downloadEmptyBox(UpOrDownPolicy upOrDownPolicy) {
        if (upOrDownPolicy.getProcessType().equals(Constants.STATE_PROCESS_TYPE_ZERO) && !StrUtil.isEmpty(upOrDownPolicy.getBoxCode())) {
            Param param = new Param();
            GoodsBox goodsBox = new GoodsBox();
            goodsBox.setBoxCode(upOrDownPolicy.getBoxCode());
            param.setGoodsBox(goodsBox);
            //只发送一次
            Object obj = redisCache.get("outbound:boxCode:" + upOrDownPolicy.getBoxCode());
            if (obj == null) {
                log.error("空箱或指定货箱出库推送到平台:{},{}", upOrDownPolicy.getBoxCode(), JSON.toJSONString(upOrDownPolicy));
                redisCache.set("outbound:boxCode:" + upOrDownPolicy.getBoxCode(), upOrDownPolicy.getBoxCode(), 30, TimeUnit.DAYS);
                goodsBoxService.downloadEmptyBox(param);
            }
        }
    }

    /**
     * 到达缓存位后，重新发个入库指令给wcs,wcs按按钮后，直接入库
     *
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-07-23 14:15:19
     */
    public void inBufferUpOrDownPolicyToWcs(UpOrDownPolicy upOrDownPolicy, GoodsBox goodsBox) {
        Object obj = redisCache.get(CacheConstants.SORT_TASK_DOWNLOAD_WEIGHT + upOrDownPolicy.getClientId() + ":" + upOrDownPolicy.getBoxCode());
        try {
            if (obj != null) {
                Param param = new Param();
                BigDecimal sortWeight = (BigDecimal) obj;
                UpOrDownPolicy tmp = upOrDownPolicy;
                tmp.setType(Constants.BOX_OPERATION_UP);
                tmp.setId(IdUtil.getSnowflake().nextIdStr());
                tmp.setTotalWeight(sortWeight);
                tmp.setUpByButton(true);
                param.setUpOrDownPolicy(tmp);

                WcsData wcsData = new WcsData();
                wcsData.setTotalWeight(sortWeight.negate().multiply(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT)));
                List<GoodsBoxDetail> list = goodsBoxDetailService.list(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, goodsBox.getBoxCode()).gt(GoodsBoxDetail::getWeight, BigDecimal.ZERO));
                param.setOutboundAndEntry(true);
                param.setMap(wcsData);
                goodsBox.setDetailList(list);
                param.setGoodsBox(goodsBox);
                log.error("货箱编号：{}，货箱分拣重量：{}", goodsBox.getBoxCode(), sortWeight);
                wcsService.sortingEntry(param, tmp);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 货箱入库离开缓存位
     *
     * @param event
     * @param msg
     * @param key
     * @param isClear
     * @return void
     * @author yw
     * @date 2022-07-08 11:08:45
     */
    public synchronized void leaveBufferPos(String event, String msg, String key, boolean isClear, String topic) throws Exception {
        StorageDone storageDone = JSONUtil.toBean(msg, StorageDone.class);
        UpOrDownPolicy upOrDownPolicy;
        upOrDownPolicy = upOrDownPolicyService.getOne(Wrappers.<UpOrDownPolicy>lambdaQuery().eq(UpOrDownPolicy::getBoxCode, storageDone.getBoxCode()).eq(UpOrDownPolicy::getType, Constants.BOX_OPERATION_UP));
        if (upOrDownPolicy != null) {
            MessageInfo messageInfo = new MessageInfo();
            /**相应正常无需返回web,异常则返回web提示* */
            if (Constants.ANSWER_ID_OK == storageDone.getAnswerID()) {

                GoodsBox goodsBox = goodsBoxService.getGoodsBoxByKey(upOrDownPolicy);

                String redisKey = CacheConstants.WAREHOUSING_ENTRY_WEIGHT + upOrDownPolicy.getBoxCode() + ":" + upOrDownPolicy.getClientId();
                redisCache.del(redisKey);
                String inBufferKey = "inBufferPos" + Constants.TYPE_WCS_RESPONSE + upOrDownPolicy.getClientId();
                /**离开库位后，清除到位缓存，处理下一个**/
                List<Object> objectList = redisCache.getList(inBufferKey, 0, -1);
                log.error("leaveBufferPos:inBuffer:{}", JSONUtil.toJsonStr(objectList));
                if (objectList != null && !objectList.isEmpty()) {
                    List<Object> listObject = new ArrayList<>();
                    for (Object object : objectList) {
                        MessageInfo message = (MessageInfo) object;
                        Param param = (Param) message.getData();
                        if (!(param != null && param.getGoodsBox() != null && param.getGoodsBox().getBoxCode().equals(upOrDownPolicy.getBoxCode()))) {
                            listObject.add(object);
                        }
                    }
                    redisCache.del(inBufferKey);
                    if (!listObject.isEmpty()) {
                        redisCache.setAllListOfRight(inBufferKey, listObject);
                    }
                }
                //分拣的货箱仓库编号
                redisCache.del(CacheConstants.SORT_TASK_SEAT_ID + upOrDownPolicy.getClientId() + upOrDownPolicy.getBoxCode());
                if (goodsBox == null) {
                    goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, upOrDownPolicy.getBoxCode()));
                }
                goodsBox.setStatus(Constants.GOODS_BOX_STATE_ZERO);

                /**批量连续出库，处理逻辑：1、响应成功的时候，发下一个到wcs;2、wcs消息队列满的时候，
                 * 货箱离开货位响应的时候，发一个；3、货箱离开库位的时候，发一个同品类的；**/
//                sortOutSameSpu(goodsBox, upOrDownPolicy, topic, true, messageInfo);
                GoodsBox finalGoodsBox = goodsBox;
                ThreadUtil.execAsync(() -> {
                    try {
                        sortOutFromDownloadLast(finalGoodsBox, upOrDownPolicy, topic, true, messageInfo);
                    } catch (Exception e) {
                        log.error("leaveBufferPos出库异常：{}", e);
                    }

                });

                //inBufferPos >> downloadEmptyBox函数
                //空箱出库，清除指定空箱出库货箱，如果wcs未收到响应会重复发，会一直新增库位到平台，
                // 空箱出库到达缓存位做个限制，存放redis，存在则不更新，离开缓存位即入库后删除，
                // 包含所有离开缓存位都会删，入分拣，空箱入库，采购入库等
                redisCache.del("outbound:boxCode:" + upOrDownPolicy.getBoxCode());

                leaveBufferPosInfo(upOrDownPolicy, event, messageInfo, isClear, goodsBox, storageDone, topic, msg);

            } else {
                log.error("leaveBufferPos响应异常：{}", JSONUtil.toJsonStr(storageDone));
            }
        } else {
            log.error("leaveBufferPos指令异常：{}", JSONUtil.toJsonStr(storageDone));
        }

        WcsResponse fallback = new WcsResponse();
        fallback.setAnswerID(Constants.ANSWER_ID_OK);
        fallback.setBoxCode(storageDone.getBoxCode());
        wcsService.sendDelay();

        String json = JSONUtil.toJsonStr(fallback);
        mqttService.sendToMqtt(Constants.TOPIC_MQTT_LEAVE_BUFFER_POS_ANSWER, json);
        mqttRecordService.initMqttRecord(storageDone.getBoxCode(), Constants.TOPIC_MQTT_LEAVE_BUFFER_POS_ANSWER, 0, json);
    }

    /**
     * @Description 离开缓存位后获取货箱数据（调整放到GoodsBoxService）
     * @Param
     * @Author yw
     * @Date 2024/6/22 15:40
     * @Return
     **/
    @Deprecated
    public GoodsBox getGoodsBoxByKey(UpOrDownPolicy upOrDownPolicy) {
        //货箱入库离开库位响应再保存货箱信息
        String boxKey = CacheConstants.WCS_RESPONSE + upOrDownPolicy.getBoxCode() + ":" + upOrDownPolicy.getProcessType();
        Param paramInfo = redisCache.getCacheObject(boxKey);
        GoodsBox goodsBox = null;
        if (paramInfo != null) {
            if (paramInfo.getGoodsBox() != null) {
                boolean fl = goodsBoxService.saveOrUpdate(paramInfo.getGoodsBox());
                goodsBox = paramInfo.getGoodsBox();
                if (fl && paramInfo.getList() != null && !paramInfo.getList().isEmpty()) {
                    goodsBoxDetailService.saveOrUpdateBatch(paramInfo.getList());
                    paramInfo.setGoodsBox(goodsBox);
                    redisCache.set(boxKey, paramInfo, 3, TimeUnit.DAYS);
                }
            }
        }
        return goodsBox;
    }

    @Transactional(rollbackFor = Exception.class)
    public void leaveBufferPosInfo(UpOrDownPolicy upOrDownPolicy, String event, MessageInfo messageInfo, boolean isClear, GoodsBox goodsBox, StorageDone storageDone, String topic, String msg) throws Exception {
        messageInfo.setUpByButton(upOrDownPolicy.isUpByButton());
        responseWeb(event, upOrDownPolicy, messageInfo, isClear, false, goodsBox);
        mqttRecordService.initMqttRecord(storageDone.getBoxCode(), topic, upOrDownPolicy.getDataState(), msg);
    }

    /**
     * 出库同批次的下一个货箱
     * <p>
     * 货位离开缓存位响应，出库待出库货箱，若是待出库货箱内有正在出库，或者出库中的相同spu，则不出库
     *
     * @param goodsBox
     * @param upOrDownPolicy
     * @param topic
     * @return void
     * @author yw
     * @date 2022-07-17 15:46:15
     */
    public void sortOutFromDownloadLast(GoodsBox goodsBox, UpOrDownPolicy upOrDownPolicy, String topic, boolean leaveInBuffer, MessageInfo messageInfo) throws Exception {
        /**从获取分拣任务时，同一个spu有多个货箱的另存到的另一个key中获取列表，与当前离开缓存位准备入库的货箱进行对比，如果spu相同，则出库**/
        List<GoodsBoxDetail> goodsBoxDetails = goodsBoxDetailService.list(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, goodsBox.getBoxCode()).gt(GoodsBoxDetail::getWeight, BigDecimal.ZERO));

        if (!goodsBoxDetails.isEmpty()) {
            Spu spu = spuService.getById(goodsBoxDetails.get(0).getSpuId());
            if (spu != null) {
                messageInfo.setTemperatureCode(String.valueOf(spu.getTemperatureZoneCode()));
            }
        }
        log.error("sortOutFromDownloadLast同批次detail：" + JSONUtil.toJsonStr(goodsBoxDetails));
        List<Object> objectList = redisCache.getList(CacheConstants.SORT_TASK_DOWNLOAD_LAST + upOrDownPolicy.getClientId(), 0, -1);
        //出库中的redis货箱列表
        List<Object> loadingList = redisCache.getList(CacheConstants.SORT_TASK_DOWNLOAD + upOrDownPolicy.getClientId(), 0, -1);
        if (objectList != null && !objectList.isEmpty()) {
            //单出库列表没出库任务的时候，再从待出库列表中获取出库货箱出库
//            if (loadingList == null || loadingList.isEmpty()) {
            log.error("sortOutFromDownloadLast同批次：" + JSONUtil.toJsonStr(objectList));
            //单个弹出存在货箱都是同一仓库，同一温区情况，其他仓库都排到后面，有只会出库一个仓库的概率，其他仓库有货箱却没出库
            //改为循环去判断
            List<Object> boxCodeList = new ArrayList<>();
            List<Object> downloadLast = new ArrayList<>();
            //弹出的在途货箱,待出库货箱校验可以出库后，弹出的货箱算作是在途货箱
            List<GoodsBoxDetail> goodsBoxDetailList = new ArrayList<>();
            //获取出库中货箱详情列表
            if (loadingList != null && !loadingList.isEmpty()) {
                for (Object object : loadingList) {
                    String boxCode = String.valueOf(object);
                    List<GoodsBoxDetail> boxDetailList = goodsBoxDetailService.list(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, boxCode).gt(GoodsBoxDetail::getWeight, BigDecimal.ZERO));
                    if (!boxDetailList.isEmpty()) {
                        goodsBoxDetailList.addAll(boxDetailList);
                    }
                }
            }

            /**待出库列表的货箱详情与在途货箱以及出库中中的货箱详情对比，在待出库列表中，有一个spu时存在在在途货箱以及出库中货箱中，则待出库列表的货箱不能出库**/
            for (Object object : objectList) {
                boolean flag = false;
                GoodsBoxDetail goodsBoxDetail = JSONUtil.toBean(JSONUtil.toJsonStr(object), GoodsBoxDetail.class);
                List<GoodsBoxDetail> list = goodsBoxDetailService.list(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, goodsBoxDetail.getBoxCode()).gt(GoodsBoxDetail::getWeight, BigDecimal.ZERO));
                //在途的货箱详情
                List<GoodsBoxDetail> downloadingList = goodsBoxDetailService.selectByUpOrDownPolicyType(Constants.BOX_OPERATION_DOWN, Constants.STATE_PROCESS_TYPE_ONE);
                downloadingList.addAll(goodsBoxDetailList);
                LinkedHashMap<String, List<GoodsBoxDetail>> mapSpu = downloadingList.stream().collect(Collectors.groupingBy(GoodsBoxDetail::getSpuId, LinkedHashMap::new, Collectors.toList()));
                if (!mapSpu.isEmpty()) {
                    for (GoodsBoxDetail goodsBoxDetailInfo : list) {
                        if (mapSpu.containsKey(goodsBoxDetailInfo.getSpuId())) {
                            for (GoodsBoxDetail detail : mapSpu.get(goodsBoxDetailInfo.getSpuId())) {
                                if (detail.getSkuCode().equals(goodsBoxDetailInfo.getSkuCode())) {
                                    flag = true;
                                    break;
                                }
                            }
                        }
                    }
                }
                if (!flag) {
                    boxCodeList.add(goodsBoxDetail.getBoxCode());
                    goodsBoxDetailList.add(goodsBoxDetail);
                } else {
                    downloadLast.add(object);
                }
            }

            //重置待出库redis
            redisCache.del(CacheConstants.SORT_TASK_DOWNLOAD_LAST + upOrDownPolicy.getClientId());
            if (!downloadLast.isEmpty()) {
                redisCache.setAllListOfRight(CacheConstants.SORT_TASK_DOWNLOAD_LAST + upOrDownPolicy.getClientId(), downloadLast);
            }

            //放入出库中列表
            if (!boxCodeList.isEmpty()) {
                redisCache.setAllListOfRight(CacheConstants.SORT_TASK_DOWNLOAD + upOrDownPolicy.getClientId(), boxCodeList);
            }
//            }
            sortingOutBatchInfo(upOrDownPolicy, topic, null, leaveInBuffer);
        } else if (loadingList != null && !loadingList.isEmpty()) {
            Param param = new Param();
            param.setUpOrDownPolicy(upOrDownPolicy);
            sortingOutBatchInfo(upOrDownPolicy, topic, param, leaveInBuffer);
        }
    }

    /**
     * 当前入库货箱，都分拣完毕或者从待出库中未找到，
     * 校验出库中是否有货箱出库，有则发指令，无，则从待出库中发指令
     *
     * @param detailList
     * @param size
     * @param upOrDownPolicy
     * @param topic
     * @param lastDownloadList
     * @return void
     * @author yw
     * @date 2022-08-19 18:31:58
     */
    public void boxSpuSortDone(List<GoodsBoxDetail> detailList, Long size, UpOrDownPolicy upOrDownPolicy, String topic, List<Object> lastDownloadList, boolean leaveInBuffer) throws Exception {
        //detailList 为空，说明待出库有数据，但是spu不再goodsBoxDetails列表中或者是goodsBoxDetails为空
        //上面for循环不会从待出库redis列表弹出数据去出库
        //则：查看出库中列表是否有出库数据，有则发送出库指令，没有则从待出库redis列表出弹出一个出库
        if (detailList.isEmpty()) {
            if (size != null && size > 0) {
                Param param = new Param();
                param.setUpOrDownPolicy(upOrDownPolicy);
                sortingOutBatchInfo(upOrDownPolicy, topic, param, leaveInBuffer);
            } else {
                //单个弹出存在货箱都是同一仓库，同一温区情况，其他仓库都排到后面，有只会出库一个仓库的概率，其他仓库有货箱却没出库
                //改为循环去判断
//                Object object = redisCache.delListLeftPop(CacheConstants.SORT_TASK_DOWNLOAD_LAST + upOrDownPolicy.getClientId());
                List<Object> objectList = redisCache.getList(CacheConstants.SORT_TASK_DOWNLOAD_LAST + upOrDownPolicy.getClientId(), 0, -1);
                if (objectList != null && !objectList.isEmpty()) {
                    List<Object> boxCodeList = new ArrayList<>();
                    List<Object> downloadLast = new ArrayList<>();
                    //弹出的在途货箱,待出库货箱校验可以出库后，弹出的货箱算作是在途货箱
                    List<GoodsBoxDetail> goodsBoxDetailList = new ArrayList<>();
                    for (Object object : objectList) {
                        boolean flag = false;
                        /**待出库列表的货箱详情与在途货箱中的货箱详情对比，自在待出库列表中，有一个spu时存在在在途货箱中，则待出库列表的货箱不能出库**/
                        GoodsBoxDetail goodsBoxDetail = JSONUtil.toBean(JSONUtil.toJsonStr(object), GoodsBoxDetail.class);
                        List<GoodsBoxDetail> list = goodsBoxDetailService.list(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, goodsBoxDetail.getBoxCode()).gt(GoodsBoxDetail::getWeight, BigDecimal.ZERO));
                        //在途的货箱详情
                        List<GoodsBoxDetail> downloadingList = goodsBoxDetailService.selectByUpOrDownPolicyType(Constants.BOX_OPERATION_DOWN, Constants.STATE_PROCESS_TYPE_ONE);
                        if (downloadingList != null && !downloadingList.isEmpty()) {
                            if (!goodsBoxDetailList.isEmpty()) {
                                downloadingList.addAll(goodsBoxDetailList);
                            }
                            LinkedHashMap<String, List<GoodsBoxDetail>> map = downloadingList.stream().collect(Collectors.groupingBy(GoodsBoxDetail::getSpuId, LinkedHashMap::new, Collectors.toList()));
                            if (!map.isEmpty()) {
                                for (GoodsBoxDetail goodsBoxDetailInfo : list) {
                                    if (map.containsKey(goodsBoxDetailInfo.getSpuId())) {
                                        for (GoodsBoxDetail detail : map.get(goodsBoxDetailInfo.getSpuId())) {
                                            if (detail.getSkuCode().equals(goodsBoxDetailInfo.getSkuCode())) {
                                                flag = true;
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        if (!flag) {
                            boxCodeList.add(goodsBoxDetail.getBoxCode());
                            goodsBoxDetailList.add(goodsBoxDetail);
                        } else {
                            downloadLast.add(object);
                        }
                    }

                    //重置待出库redis
                    redisCache.del(CacheConstants.SORT_TASK_DOWNLOAD_LAST + upOrDownPolicy.getClientId());
                    if (!downloadLast.isEmpty()) {
                        redisCache.setAllListOfRight(CacheConstants.SORT_TASK_DOWNLOAD_LAST + upOrDownPolicy.getClientId(), downloadLast);
                    }

                    //放入出库中列表
                    if (!boxCodeList.isEmpty()) {
                        redisCache.setAllListOfRight(CacheConstants.SORT_TASK_DOWNLOAD + upOrDownPolicy.getClientId(), boxCodeList);
                    }

                    sortingOutBatchInfo(upOrDownPolicy, topic, null, leaveInBuffer);
                }
            }
        }
    }

    /**
     * 入库完成,更新仓库和货箱库位信息
     *
     * @param event
     * @param msg
     * @return void
     * @author yw
     * @date 2022-03-31 11:16:52
     */
    @Transactional(rollbackFor = Exception.class)
    public void storageDone(String event, String msg, String topic) throws Exception {
        StorageDone response = JSONUtil.toBean(msg, StorageDone.class);
        UpOrDownPolicy upOrDownPolicy = upOrDownPolicyService.getOne(Wrappers.<UpOrDownPolicy>lambdaQuery().eq(UpOrDownPolicy::getBoxCode, response.getBoxCode()).eq(UpOrDownPolicy::getType, Constants.BOX_OPERATION_UP));
        GoodsBox goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, response.getBoxCode()));
        int state = 0;
        if (goodsBox != null) {
            /**更新货箱信息**/
            goodsBox.setWarehouseNumber(response.getSeatID());
            goodsBox.setWarehouseRow(response.getLineID());
            goodsBox.setWarehouseColumn(response.getListID());
            goodsBox.setTemperatureZoneCode(response.getTempNum());
            goodsBox.setActTemperatureCode(String.valueOf(response.getTempNum()));
            //忘了为啥要这样处理了，暂时先留着
            if (upOrDownPolicy != null && !StrUtil.isEmpty(upOrDownPolicy.getBoxType())) {
                goodsBox.setBoxType(upOrDownPolicy.getBoxType());
            }
            goodsBox.setStatus(Constants.GOODS_BOX_STATE_THREE);

            /**更新仓库信息**/
            saveOrUpdateShelfLocation(response);

            //货箱编号为空，则为wcs的数据的备份,wcs自动挪库，如：禁用，wcs设置禁用后，会响应wms,wms则进行数据存储
            if (upOrDownPolicy != null) {
                //入库完成，则设置可以删除指令（wcs 加按钮入库要）
                /**响应到web**/
                responseWeb(event, upOrDownPolicy, new MessageInfo(), true, true, goodsBox);
                state = upOrDownPolicy.getDataState();
            } else {
                goodsBoxService.updateById(goodsBox);
            }
        } else {
            log.error("storageDone货箱信息不存在，参数为：", JSON.toJSONString(response));
        }
        mqttRecordService.initMqttRecord(response.getBoxCode(), topic, state, msg);
        /**返回wcs响应**/
        storageDoneCallBackToWcs(response);
    }

    /**
     * 修改或更新货架信息
     *
     * @param response
     * @return void
     * @author yw
     * @date 2022-07-03 10:03:06
     */
    public void saveOrUpdateShelfLocation(StorageDone response) throws Exception {

        /**更新仓库信息**/
        ShelfLocation shelfLocation = new ShelfLocation();
        shelfLocation.setSeatID(response.getSeatID());
        shelfLocation.setLineID(response.getLineID());
        shelfLocation.setListID(response.getListID());
        shelfLocation.setState(response.getState());
        shelfLocation.setTempNum(response.getTempNum());
        shelfLocation.setDataType(1);
        if (!StrUtil.isEmpty(response.getBoxCode())) {
            shelfLocation.setBoxCode(response.getBoxCode());
        } else {
            shelfLocation.setBoxCode(null);
        }

        /**数据库没有，则新增**/
        ShelfLocation location = shelfLocationService.getOne(Wrappers.<ShelfLocation>lambdaQuery().eq(ShelfLocation::getSeatID, response.getSeatID()).eq(ShelfLocation::getLineID, response.getLineID()).eq(ShelfLocation::getListID, response.getListID()));
        if (location != null) {
            shelfLocation.setId(location.getId());
            LambdaUpdateWrapper<ShelfLocation> shelfLocationWrapper = new LambdaUpdateWrapper<>();
            shelfLocationWrapper.eq(ShelfLocation::getSeatID, response.getSeatID()).eq(ShelfLocation::getLineID, response.getLineID()).eq(ShelfLocation::getListID, response.getListID());
            shelfLocationService.update(shelfLocation, shelfLocationWrapper);
        } else {
            shelfLocationService.save(shelfLocation);
        }
    }

    /**
     * 入库完成，响应wcs
     *
     * @param response
     * @return void
     * @author yw
     * @date 2022-07-03 10:03:24
     */
    public void storageDoneCallBackToWcs(StorageDone response) throws Exception {
        WcsResponse fallback = new WcsResponse();
        fallback.setAnswerID(Constants.ANSWER_ID_OK);
        fallback.setBoxCode(response.getBoxCode());
        wcsService.sendDelay();
        System.out.println("入库到位反馈响应：" + JSONUtil.toJsonStr(fallback));

        String json = JSONUtil.toJsonStr(fallback);
        mqttService.sendToMqtt(Constants.TOPIC_MQTT_STORAGE_OVER, json);
        mqttRecordService.initMqttRecord(response.getBoxCode(), Constants.TOPIC_MQTT_STORAGE_OVER, 0, json);
    }

    /**
     * 响应返回到web
     *
     * @param event          返回web事件
     * @param upOrDownPolicy 指令
     * @param messageInfo    返回web信息
     * @param isClear        是否清除指令
     * @param isDone         是否是结束流程的响应
     * @return void
     * @author yw
     * @date 2022-04-07 15:11:12
     */
    public void responseWeb(String event, UpOrDownPolicy upOrDownPolicy, MessageInfo messageInfo, boolean isClear, boolean isDone, GoodsBox goodsBox) throws Exception {

        initMessageInfo(upOrDownPolicy, Constants.SUCCESS, messageInfo);
        //流程类型：0、客户取货出库；1、分拣/生产；2、称重；3、入库；4、强制入库；5、货箱毛重称重；6、分拣的订单箱入库
        //先写到这，懒得一个一个流程写
        log.error("responseWeb:{}", JSON.toJSONString(upOrDownPolicy));
        log.error("responseWeb:{}", JSON.toJSONString(goodsBox));
        switch (upOrDownPolicy.getProcessType()) {
            case Constants.STATE_PROCESS_TYPE_ZERO:
                if (!StrUtil.isEmpty(upOrDownPolicy.getOutboundOrderCode())) {
                    //客户取货出库
                    outbound(upOrDownPolicy, isDone, goodsBox);
                    List<OutboundOrderDetailDto> list = outboundOrderDetailService.outboundCount(upOrDownPolicy.getOutboundOrderCode());
                    messageInfo.setOutboundOrderDetailDtos(list);
                }
                //出库不入库指令，到达缓存位，不放入缓存，指令响应成功，不响应到web
                //应为Constants.STATE_TO_WCS_EIGHT,第一个货箱会有wcs_to_wms响应，流程会有多次响应，加1
                if (upOrDownPolicy.getProcessState().equals(Constants.STATE_TO_WCS_EIGHT) || upOrDownPolicy.getProcessState().equals(Constants.STATE_TO_WCS_NINE)) {
                    event = "";
                }
                break;
            case Constants.STATE_PROCESS_TYPE_ONE:
                //分拣/生产
                sort(upOrDownPolicy);
                //分拣出库响应，响应成功，则发下一个需要出库分拣的箱子
                /*log.error("分拣出库响应，响应成功，则发下一个需要出库分拣的箱子" + JSONUtil.toJsonStr(upOrDownPolicy));
                if (MqConstants.STATE_TO_WCS_ONE == upOrDownPolicy.getProcessState()) {
                    //todo 继续发送分家需要下架的箱子
                    String finalEvent = event;
                    ThreadUtil.execAsync(() -> {
                        sortingOutBatchInfo(upOrDownPolicy, finalEvent, null);
                    });
                }*/
                break;
            case Constants.STATE_PROCESS_TYPE_TWO:
                //分拣中称重
                sortWeight(event, upOrDownPolicy, messageInfo);
                break;
            case Constants.STATE_PROCESS_TYPE_THREE:
                //入库
                messageInfo = entry(event, upOrDownPolicy, isDone, messageInfo, goodsBox);
                break;
            case Constants.STATE_PROCESS_TYPE_FOUR:
                //强制入库
                entryForce(event, upOrDownPolicy, messageInfo, isDone, goodsBox);
                //如果等于离开货位响应，响应到web更新一下展示数据
                if (!"leaveBufferPos".equals(event)) {
                    event = "";
                }
                break;
            case Constants.STATE_PROCESS_TYPE_FIVE:
                //净重称重
                boolean result = netWeight(event, upOrDownPolicy, isDone, messageInfo);
                /**称重一次指令分两次响应，一次是发送指令时，能存到消息队列里的响应，一次是货箱放到缓存位时，称重后的包含货箱的重量响应**/
                if (result) {
                    isClear = true;
                    event = "";
                } else {
                    isClear = false;
                    event = "weighResp";
                }
                break;
            case Constants.STATE_PROCESS_TYPE_SIX:
                //分拣的订单箱入库
                break;
        }

        clearPolicy(upOrDownPolicy, upOrDownPolicy.getProcessState(), isClear);

        if (!StrUtil.isEmpty(event)) {
            //todo 做个测试web发数据后（后台延时返回web），然后web关闭，然后再打开，看数据发送情况，是否会发送，以什么方式发送
            Object uuid = redisCache.get(CacheConstants.REDIS_KEY_SOCKET_CLIENT + ":" + upOrDownPolicy.getClientId());
            if (uuid != null) {
                UUID id = UUID.fromString(String.valueOf(uuid));
                //做个缓存，web接收到后，触发删除
//            redisCache.set(CacheConstants.REDIS_KEY_SOCKET_CLIENT + ":" + upOrDownPolicy.getClientId() + ":" + upOrDownPolicy.getBoxCode(), messageInfo);
                this.socketIo(id, event, messageInfo, upOrDownPolicy, AjaxResult.success(messageInfo.getMsg()));
            }

        }
    }

    /**
     * 强制入库逻辑处理
     *
     * @param event
     * @param upOrDownPolicy
     * @param messageInfo
     * @param isDone
     * @return void
     * @author yw
     * @date 2022-07-06 11:38:08
     */
    public void entryForce(String event, UpOrDownPolicy upOrDownPolicy, MessageInfo messageInfo, boolean isDone, GoodsBox goodsBox) throws Exception {
        /**强制入库，更新货箱重量为称重返回重量，暂时以强制入库能获取的货箱详情的总重量为货箱重量**/
        /*List<GoodsBoxDetail> list = goodsBoxDetailService.list(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, upOrDownPolicy.getBoxCode()));
        BigDecimal totalWeight = BigDecimal.ZERO;
        if (list != null && !list.isEmpty()) {
            totalWeight = list.stream().map(GoodsBoxDetail::getWeight).reduce(BigDecimal.ZERO, BigDecimal::add);
            goodsBox.setWeight(totalWeight);
        }*/
        //强制入库，取实际重量
        if (upOrDownPolicy.getTotalWeight() != null && upOrDownPolicy.getTotalWeight().compareTo(BigDecimal.ZERO) > 0) {
            goodsBox.setWeight(upOrDownPolicy.getTotalWeight().subtract(goodsBox.getBoxStandardWeight()));
        }
        //todo 生成一条异常强制入库的异常记录，哪个货箱重量不匹配等信息
        entry(event, upOrDownPolicy, isDone, messageInfo, goodsBox);
    }

    /**
     * 货箱毛重称重，然后发送到数据转发中心
     *
     * @param event
     * @param upOrDownPolicy
     * @param isDone
     * @param messageInfo
     * @return boolean
     * @author yw
     * @date 2022-06-08 11:31:08
     */
    public boolean netWeight(String event, UpOrDownPolicy upOrDownPolicy, boolean isDone, MessageInfo messageInfo) throws Exception {
        //若是货箱净重称重响应，则更新货箱标准重量
//        WcsResponse response = JSONUtil.toBean(JSONUtil.toJsonStr(messageInfo.getData()), WcsResponse.class);
        GoodsBox model = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, upOrDownPolicy.getBoxCode()));
        if (upOrDownPolicy.getTotalWeight().compareTo(BigDecimal.ZERO) > 0) {
            //暂时不考虑异步，因为新货箱称重完成会马上入库，异步更新数据会延时
            netWeightUpdateGoodsBox(model, upOrDownPolicy);
            return true;
        } else {
            //do something
            return false;
        }
    }

    /**
     * 星火乡称重，更新货箱信息，以及从数据中心获取生产商
     *
     * @param model
     * @param upOrDownPolicy
     * @author yw
     * @date 2022-06-08 11:31:08
     */
    public void netWeightUpdateGoodsBox(GoodsBox model, UpOrDownPolicy upOrDownPolicy) {
        if (model == null) {
            model = goodsBoxService.getGoodsBoxByKey(upOrDownPolicy);
        }
        //取第三位的值，判断大小货箱
        model.setType(Integer.parseInt(model.getBoxCode().substring(2, 3)));
        List<SysDictData> list = DictUtils.getDictCache(com.lvy.hczn.front.common.constant.Constants.BOX_TYPE_ERROR_RANGE);
        List<SysDictData> boxSizeList = DictUtils.getDictCache(com.lvy.hczn.front.common.constant.Constants.BOX_SIZE_INFO);
        LinkedHashMap<String, List<SysDictData>> boxCodeMap = new LinkedHashMap<>();
        if (boxSizeList != null && !boxSizeList.isEmpty()) {
            boxCodeMap = boxSizeList.stream().collect(Collectors.groupingBy(SysDictData::getDictLabel, LinkedHashMap::new, Collectors.toList()));
        }
        /*设置大小货箱误差范围**/
        if (list != null && !list.isEmpty()) {
            for (SysDictData sysDictData : list) {
                if (model.getType() == com.lvy.hczn.front.common.constant.Constants.BOX_SIZE_BIG) {
                    //大货箱
                    if (sysDictData.getDictLabel().equals(com.lvy.hczn.front.common.constant.Constants.BOX_ERROR_MAX_BIG)) {
                        model.setErrorMax(new BigDecimal(sysDictData.getDictValue()));
                    } else if (sysDictData.getDictLabel().equals(com.lvy.hczn.front.common.constant.Constants.BOX_ERROR_MAX_SMALL)) {
                        model.setErrorMin(new BigDecimal(sysDictData.getDictValue()));
                    }
                    /*WMS根据货箱编号可确定货箱大小，在设置货箱大小规格时，平台端展示没有可以根据货箱编号设置大小的方式，暂时用货箱占位确认大小。
                     * 如，平台端货箱规格管理表加个字段：type，0大货箱，1小货箱**/
                    if (boxCodeMap.containsKey(String.valueOf(com.lvy.hczn.front.common.constant.Constants.BOX_SIZE_DIGIT_CAPACITY_BIG))) {
                        SysDictData boxSizeData = boxCodeMap.get(String.valueOf(com.lvy.hczn.front.common.constant.Constants.BOX_SIZE_DIGIT_CAPACITY_BIG)).get(0);
                        GoodsBoxSpec goodsBoxSpec = goodsBoxSpecService.getOne(Wrappers.<GoodsBoxSpec>lambdaQuery().eq(GoodsBoxSpec::getDigitCapacity, boxSizeData.getDictValue()).last("limit 1"));
                        if (goodsBoxSpec != null) {
                            model.setGoodsBoxSpecId(goodsBoxSpec.getSpecCode());
                        } else {
                            log.error("数据库未配置大货箱占位大小：{}", com.lvy.hczn.front.common.constant.Constants.BOX_SIZE_DIGIT_CAPACITY_BIG);
                        }
                    } else {
                        log.error("数据字典未配置大货箱占位大小：{}", com.lvy.hczn.front.common.constant.Constants.BOX_SIZE_DIGIT_CAPACITY_BIG);
                    }
                } else if (model.getType() == com.lvy.hczn.front.common.constant.Constants.BOX_SIZE_SMALL) {
                    //小货箱
                    if (sysDictData.getDictLabel().equals(com.lvy.hczn.front.common.constant.Constants.BOX_ERROR_MIN_BIG)) {
                        model.setErrorMax(new BigDecimal(sysDictData.getDictValue()));
                    } else if (sysDictData.getDictLabel().equals(com.lvy.hczn.front.common.constant.Constants.BOX_ERROR_MIN_SMALL)) {
                        model.setErrorMin(new BigDecimal(sysDictData.getDictValue()));
                    }
                    /*WMS根据货箱编号可确定货箱大小，在设置货箱大小规格时，平台端展示没有可以根据货箱编号设置大小的方式，暂时用货箱占位确认大小。
                     * 如，平台端货箱规格管理表加个字段：type，0大货箱，1小货箱**/
                    if (boxCodeMap.containsKey(String.valueOf(com.lvy.hczn.front.common.constant.Constants.BOX_SIZE_DIGIT_CAPACITY_SMALL))) {
                        SysDictData boxSizeData = boxCodeMap.get(String.valueOf(com.lvy.hczn.front.common.constant.Constants.BOX_SIZE_DIGIT_CAPACITY_SMALL)).get(0);
                        GoodsBoxSpec goodsBoxSpec = goodsBoxSpecService.getOne(Wrappers.<GoodsBoxSpec>lambdaQuery().eq(GoodsBoxSpec::getDigitCapacity, boxSizeData.getDictValue()).last("limit 1"));
                        if (goodsBoxSpec != null) {
                            model.setGoodsBoxSpecId(goodsBoxSpec.getSpecCode());
                        } else {
                            log.error("数据库未配置小货箱占位大小：{}", com.lvy.hczn.front.common.constant.Constants.BOX_SIZE_DIGIT_CAPACITY_SMALL);
                        }
                    } else {
                        log.error("数据字典未配置小货箱占位大小：{}", com.lvy.hczn.front.common.constant.Constants.BOX_SIZE_DIGIT_CAPACITY_SMALL);
                    }
                }
            }
        }

        /*从平台获取货箱信息，取得生产商信息，更新到本地货箱数据（箱子出入库需推货箱生产商信息）**/
        String url = DictUtils.getDictValue(com.lvy.hczn.front.common.constant.Constants.INTER_TRANSMIT_DICT_TYPE, com.lvy.hczn.front.common.constant.Constants.SYS_DICT_MERGE_BOX_SUPPLIER_INFO);
        String domain = DictUtils.getDictValue(com.lvy.hczn.front.common.constant.Constants.PLATFORM_DOMAIN, com.lvy.hczn.front.common.constant.Constants.DATA_TRANSMIT_INTER);

        if (StrUtil.hasEmpty(url, domain)) {
            log.error("出库单域名或接口地址为空:url:{},domain:{}", url, domain);
            upOrDownPolicyService.delPhysicalById(upOrDownPolicy.getId());
            throw new UtilException("出库单域名或接口地址为空");
        }
        ParamInfo<GoodsBox> param = new ParamInfo<>();
        GoodsBox goodsBoxInfos = new GoodsBox();
        goodsBoxInfos.setBoxCode(model.getBoxCode());
        param.setData(goodsBoxInfos);
        String content = JSON.toJSONString(param);
        String codeResult = HttpRequest.post(domain + url).body(content).execute().body();
        log.error("新货箱称重获取货箱信息返回：" + codeResult);
        AjaxResult codeAjaxResult = JSON.parseObject(codeResult, AjaxResult.class);
        if (codeAjaxResult.isSuccess()) {
            String object = JSON.toJSONString(codeAjaxResult.get(AjaxResult.DATA_TAG));
            ParamInfo paramInfo = JSON.parseObject(object, ParamInfo.class);
            if (paramInfo != null && paramInfo.getData() != null) {
                GoodsBox goodsBoxInfo = JSON.parseObject(JSON.toJSONString(paramInfo.getData()), GoodsBox.class);
                model.setCreateBy(goodsBoxInfo.getCreateBy());
                model.setBoxStandardWeight(upOrDownPolicy.getTotalWeight());
                model.setGoodsBoxSpecId(goodsBoxInfo.getGoodsBoxSpecId());
                model.setStatus(Constants.GOODS_BOX_STATE_FIVE);
                log.error("新货箱称重同步信息：{}", JSON.toJSONString(model));
                Param paramInfos = new Param();
                paramInfos.setGoodsBox(model);
                redisCache.set(CacheConstants.WCS_RESPONSE + upOrDownPolicy.getBoxCode() + ":" + upOrDownPolicy.getProcessType(), paramInfos, 3, TimeUnit.DAYS);
                if (goodsBoxService.updateById(model)) {
                    try {
                        goodsBoxService.netWeightSyncToPlatform(model);
                    } catch (Exception e) {
                        log.error("新货箱净重称重发送异常:", e);
                        sysOperLogService.initOperLog("新货箱净重称重发送异常", domain + url, content, codeResult);
                    }
                }
            } else {
                sysOperLogService.initOperLog("货箱信息不存在", domain + url, content, codeResult);
            }
        } else {
            sysOperLogService.initOperLog("新货箱称重获取货箱信息异常", domain + url, content, codeResult);
        }
    }

    /**
     * 分拣/生产
     *
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-04-21 11:23:18
     */
    public void sort(UpOrDownPolicy upOrDownPolicy) throws Exception {
        if (upOrDownPolicy.getType().equals(Constants.BOX_OPERATION_UP)) {
            /**分拣入库指令响应成功，说明箱子原料分拣完毕，移除redis中原料list第0个元素**/
            List<String> spuList = (List<String>) redisCache.get(CacheConstants.SORT_TASK_DETAIL_SPU + "::" + upOrDownPolicy.getClientId());
            if (spuList.size() > 0) {
                spuList.remove(0);
                if (spuList.size() > 0) {
                    redisCache.set(CacheConstants.SORT_TASK_DETAIL_SPU + "::" + upOrDownPolicy.getClientId(), spuList);
                } else {
                    redisCache.del(CacheConstants.SORT_TASK_DETAIL_SPU + "::" + upOrDownPolicy.getClientId());
                }
            }
            //更改货箱为上架中
            LambdaUpdateWrapper<GoodsBox> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(GoodsBox::getBoxCode, upOrDownPolicy.getBoxCode()).set(GoodsBox::getStatus, Constants.GOODS_BOX_STATE_ZERO);
            goodsBoxService.update(updateWrapper);
//          redisCache.delListTrim(CacheConstants.SORT_TASK_DETAIL_SPU + "::" + upOrDownPolicy.getClientId(), 1L, -1L);
            if (upOrDownPolicy.getProcessState().equals(Constants.STATE_TO_WCS_TEN)) {
                //校验分拣员分拣是否完成，完成则更改状态
                sortTaskService.sortDone(upOrDownPolicy.getClientId(), Constants.SORT_TASK_STATUS_ING);
            }
        } else {
            redisCache.del(CacheConstants.SORT_TASK_DOWNLOAD + upOrDownPolicy.getClientId() + ":" + upOrDownPolicy.getBoxCode());
        }
    }

    /**
     * 分拣中称重响应
     *
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-05-29 10:39:11
     */
    public MessageInfo sortWeight(String event, UpOrDownPolicy upOrDownPolicy, MessageInfo messageInfo) {
        return messageInfo;
    }

    /**
     * 货箱出库
     *
     * @param upOrDownPolicy
     * @param isDone
     * @param goodsBox
     * @return void
     * @author yw
     * @date 2022-04-13 15:18:19
     */
    public void outbound(UpOrDownPolicy upOrDownPolicy, boolean isDone, GoodsBox goodsBox) {
        /**客户取货出库,1、更改状态为到位，2、统计展示**/
        OutboundOrderDetail outboundOrderDetail = outboundOrderDetailService.getOne(Wrappers.<OutboundOrderDetail>lambdaQuery().eq(OutboundOrderDetail::getOutboundOrderCode, upOrDownPolicy.getOutboundOrderCode()).eq(OutboundOrderDetail::getBoxCode, upOrDownPolicy.getBoxCode()));

        //批量出库空箱的时候，只输入货箱数量，没有货箱编号，会随机生成不带货箱编号的出库单，到达缓存位后，更新
        //当根据出库单和货箱编号查不到数据时，则用批量出库的方式查
        if (outboundOrderDetail == null) {
            /**影响不是很大，暂不考虑synchronized代码片**/
            List<OutboundOrderDetail> list = outboundOrderDetailService.list(Wrappers.<OutboundOrderDetail>lambdaQuery().eq(OutboundOrderDetail::getOutboundOrderCode, upOrDownPolicy.getOutboundOrderCode()).isNull(OutboundOrderDetail::getBoxCode));
            if (!list.isEmpty()) {
                outboundOrderDetail = list.get(0);
                if (goodsBox != null) {
                    outboundOrderDetail.setBoxCode(goodsBox.getBoxCode());
                } else {
                    outboundOrderDetail.setBoxCode(upOrDownPolicy.getBoxCode());
                }

            }
        }

        /**根据零除是否结束来更新信息**/
        if (outboundOrderDetail != null) {
            if (!isDone) {
                /**取货单出库夹取货箱的时候，更新状态为出库中**/
                outboundOrderDetail.setStatus(Constants.OUTBOUND_DETAIL_STATUS_ONE);
            } else {
                outboundOrderDetail.setStatus(Constants.OUTBOUND_DETAIL_STATUS_TWO);
                goodsBox.setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
                log.error("goodsBoxService.updateById4");
                goodsBoxService.updateById(goodsBox);
            }
            outboundOrderDetailService.updateById(outboundOrderDetail);
        }
    }

    /**
     * 入库
     *
     * @param upOrDownPolicy
     * @param isDone
     * @return void
     * @author yw
     * @date 2022-04-13 15:16:08
     */
    public MessageInfo entry(String event, UpOrDownPolicy upOrDownPolicy, boolean isDone, MessageInfo messageInfo, GoodsBox goodsBox) throws Exception {

        log.error("goodsBoxService.updateById1");
        goodsBoxService.updateById(goodsBox);
        //取货单存在，则属于采购入库
        if (!StrUtil.isEmpty(upOrDownPolicy.getDeliveryOrderCode())) {
            DeliveryOrder deliveryOrder = deliveryOrderService.getOne(Wrappers.<DeliveryOrder>lambdaQuery().eq(DeliveryOrder::getDeliveryOrderCode, upOrDownPolicy.getDeliveryOrderCode()));
            if (deliveryOrder != null) {
                String key = CacheConstants.REDIS_KEY_SOCKET_CLIENT + ":" + upOrDownPolicy.getClientId() + ":deliverOrderCode";
                if (!deliveryOrder.getStatus().equals(Constants.DELIVERY_ORDER_STATUS_ZERO) && redisCache.get(key) == null) {
                } else {
                    //发送入库流程的响应
                    //采购入库note：wcs,wms业务流程独立,当采购入库发送指令后，wcs响应成功，说明可以入库，即可更改状态，无需等到wcs入库完成再更改状态
                    if (upOrDownPolicy.getProcessState().equals(Constants.STATE_TO_WCS_TWELVE)) {
                        //整箱入库，不考虑货箱内每个品类物品重量
                        /*LambdaUpdateWrapper<WarehousingEntry> entryWrapper = new LambdaUpdateWrapper<>();
                        entryWrapper.eq(WarehousingEntry::getBoxCode, upOrDownPolicy.getBoxCode()).eq(WarehousingEntry::getServiceCode, upOrDownPolicy.getDeliveryOrderCode()).set(WarehousingEntry::getStatus, Constants.ENTRY_ED);
                        warehousingEntryService.update(entryWrapper);*/
                        String bgPrefix = DictUtils.getDictValue(com.lvy.hczn.front.common.constant.Constants.SYS_DICT_ORDER_TYPE, com.lvy.hczn.front.common.constant.Constants.SYS_DICT_ORDER_TYPE_BG);
                        if (upOrDownPolicy.getDeliveryOrderCode().startsWith(bgPrefix)) {
                            parcelLeaveInBuffer(event, upOrDownPolicy, isDone, messageInfo, goodsBox);
                        } else {

                            changeDeliveryInfo(deliveryOrder, upOrDownPolicy);
                        }
                    }
                    if (isDone) {
                    }
                }
            }
        }

        return messageInfo;
    }

    /**
     * 同一个货箱包裹信息修改，货箱包裹，不是按照配送单->货箱列表->包裹列表形式，需单独处理，后续重构，接口按照前面所说形式处理
     * 目前的处理方案是，如果是包裹，则根据货箱编号查询状态，做一次性更改货箱内的所有包裹的状态
     *
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-04-13 15:16:08
     */
    public void parcelLeaveInBuffer(String event, UpOrDownPolicy upOrDownPolicy, boolean isDone, MessageInfo messageInfo, GoodsBox goodsBox) {
        List<DeliveryOrderDetail> deliveryOrderDetails = deliveryOrderDetailService.list(Wrappers.<DeliveryOrderDetail>lambdaQuery().eq(DeliveryOrderDetail::getBoxCode, goodsBox.getBoxCode()).eq(DeliveryOrderDetail::getStatus, Constants.ENTRY_ING));
        deliveryOrderDetails.forEach(deliveryOrderDetail -> {
            DeliveryOrder deliveryOrder = deliveryOrderService.getOne(Wrappers.<DeliveryOrder>lambdaQuery().eq(DeliveryOrder::getDeliveryOrderCode, deliveryOrderDetail.getDeliveryOrderCode()));
            if (deliveryOrder != null) {
                UpOrDownPolicy upOrDownPolicyInfo = new UpOrDownPolicy();
                upOrDownPolicyInfo.setDeliveryOrderCode(deliveryOrder.getDeliveryOrderCode());
                upOrDownPolicyInfo.setBoxCode(goodsBox.getBoxCode());
                changeDeliveryInfo(deliveryOrder, upOrDownPolicyInfo);
            }
        });

    }

    /**
     * 修改取货单状态
     *
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2022-04-13 15:16:08
     */
    public void changeDeliveryInfo(DeliveryOrder deliveryOrder, UpOrDownPolicy upOrDownPolicy) {
        LambdaUpdateWrapper<PurchaseOrderDetail> purchaseDetailWrapper = new LambdaUpdateWrapper<>();
        purchaseDetailWrapper.eq(PurchaseOrderDetail::getBoxCode, upOrDownPolicy.getBoxCode()).eq(PurchaseOrderDetail::getPurchaseOrderCode, upOrDownPolicy.getDeliveryOrderCode()).eq(PurchaseOrderDetail::getStatus, Constants.ENTRY_ING).set(PurchaseOrderDetail::getStatus, Constants.ENTRY_ED);
        purchaseOrderDetailService.update(purchaseDetailWrapper);

        //更新入库单和采购单(货箱内单个商品入库，入库时没份物品单独称重)
        /**List<GoodsBoxDetail> goodsBoxDetails = goodsBoxDetailService.list(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, upOrDownPolicy.getBoxCode()));
         if (!goodsBoxDetails.isEmpty()) {
         List<WarehousingEntry> entries = new ArrayList<>();
         //修改入库单和采购单的状态
         for (int i = 0; i < goodsBoxDetails.size(); i++) {
         GoodsBoxDetail item = goodsBoxDetails.get(i);

         WarehousingEntry entry = warehousingEntryService.getOne(Wrappers.<WarehousingEntry>lambdaQuery().eq(WarehousingEntry::getEntryCode, item.getEntryCode()));
         if (entry != null) {
         entry.setStatus(Constants.ENTRY_ED);
         entries.add(entry);
         }

         //根据采购单，批次编号和原料确认采购单下的一个采购详情
         PurchaseOrderDetail purchaseOrderDetail = purchaseOrderDetailService.getOne(Wrappers.<PurchaseOrderDetail>lambdaQuery().eq(PurchaseOrderDetail::getPurchaseOrderCode, item.getPurchaseOrderCode()).eq(PurchaseOrderDetail::getBatchCode, item.getBatchCode()).eq(PurchaseOrderDetail::getSpuId, item.getSpuId()).eq(PurchaseOrderDetail::getSkuCode, item.getSkuCode()).eq(PurchaseOrderDetail::getSpecCode, item.getSpecCode()));
         if (purchaseOrderDetail != null) {
         //采购数量等于入库数量时，采购单状态设置为已入库
         if (purchaseOrderDetail.getEntryQuantity() == null) {
         purchaseOrderDetail.setEntryQuantity(0);
         }

         purchaseOrderDetail.setEntryQuantity(purchaseOrderDetail.getEntryQuantity() + 1);
         int tmp = purchaseOrderDetail.getPurchaseQuantity().compareTo(purchaseOrderDetail.getEntryQuantity());
         if (tmp == 0) {
         purchaseOrderDetail.setStatus(Constants.ENTRY_ED);
         } else if (tmp < 0) {
         //todo 还没想好怎么处理，也还没想清楚什么情况会出现这样的，或许是入库单数量大于采购单实际采购数量，源头或许会做限制，即输入入库单时做校验
         }
         purchaseOrderDetail.setEntryQuantity(purchaseOrderDetail.getPurchaseQuantity());
         purchaseOrderDetail.setStatus(Constants.ENTRY_ED);
         purchaseOrderDetailService.updateById(purchaseOrderDetail);
         }
         }
         if (!entries.isEmpty()) {
         warehousingEntryService.updateBatchById(entries);
         }
         }**/

        if (!deliveryOrder.getStatus().equals(Constants.DELIVERY_ORDER_STATUS_ONE)) {
            /**第一次入库响应，更新取货单状态为取货中**/
            deliveryOrder.setStatus(Constants.DELIVERY_ORDER_STATUS_ONE);
            deliveryOrderService.updateById(deliveryOrder);
            LambdaUpdateWrapper<DeliveryOrderDetail> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(DeliveryOrderDetail::getDeliveryOrderCode, upOrDownPolicy.getDeliveryOrderCode()).eq(DeliveryOrderDetail::getStatus, Constants.ENTRY_ING).eq(DeliveryOrderDetail::getBoxCode, upOrDownPolicy.getBoxCode()).set(DeliveryOrderDetail::getStatus, Constants.ENTRY_ED);
            deliveryOrderDetailService.update(updateWrapper);
        }
    }

    /**
     * 电机自检反馈的参数记录
     *
     * @param msg
     * @param topic
     * @return void
     * @author yw
     * @date 2022-04-03 10:39:16
     */
    public void axisData(String msg, String topic) {
        AxisData axisData = JSONUtil.toBean(msg, AxisData.class);
        List<AxisData> list = new ArrayList<>();
        String batchCode = IdUtil.getSnowflake().nextIdStr();
        for (int i = 0; i < axisData.getActPos().size(); i++) {
            AxisData model = new AxisData();
            model.setAxisNum(axisData.getAxisNum());
            model.setMaxTorque(axisData.getMaxTorque());
            model.setSetTorque(axisData.getSetTorque());
            model.setTorque(axisData.getActTorque().get(i));
            model.setPos(axisData.getActPos().get(i));
            model.setBatchCode(batchCode);
            list.add(model);
        }
        axisDataService.saveBatch(list);
    }

    /**
     * 仓库温度记录
     *
     * @param msg
     * @param topic
     * @return void
     * @author yw
     * @date 2022-04-03 10:45:55
     */
    public void thermometer(String msg, String topic) {
    }

    /**
     * 数据恢复
     *
     * @param msg
     * @param topic
     * @return void
     * @author yw
     * @date 2022-04-03 10:46:11
     */
    public synchronized void recover(String msg, String topic) throws Exception {
        ShelfLocation shelfLocation = JSONUtil.toBean(msg, ShelfLocation.class);
        ShelfLocation model;
        switch (shelfLocation.getDATA_Type()) {
            case Constants.RECOVER_TYPE_ONE:
                //货架
                model = shelfLocationService.getOne(Wrappers.<ShelfLocation>lambdaQuery().eq(ShelfLocation::getSeatID, shelfLocation.getSeatID()).eq(ShelfLocation::getLineID, shelfLocation.getLineID()).eq(ShelfLocation::getListID, shelfLocation.getListID()));
                break;
            case Constants.RECOVER_TYPE_TWO:
                //皮带
                model = shelfLocationService.getOne(Wrappers.<ShelfLocation>lambdaQuery().eq(ShelfLocation::getBeltID, shelfLocation.getBeltID()));
                break;
            case Constants.RECOVER_TYPE_THREE:
                //夹爪/提升机
                model = shelfLocationService.getOne(Wrappers.<ShelfLocation>lambdaQuery().eq(ShelfLocation::getCyID, shelfLocation.getCyID()));
                break;
            default:
                model = null;
        }
        if (model != null) {
            model.setDATA_Type(model.getDataType());
            model.setDataType(null);
            wcsService.sendDelay();
            String param = JSONUtil.toJsonStr(model);
            log.info("recover 数据恢复：{}", param);
            mqttService.sendToMqtt(Constants.TOPIC_MQTT_RECOVER_TO_WCS, param);
            mqttRecordService.initMqttRecord(model.getBoxCode(), Constants.TOPIC_MQTT_RECOVER_TO_WCS, 0, param);
        } else {
            shelfLocation.setState(1);
            shelfLocation.setBoxCode("");
            wcsService.sendDelay();
            String param = JSONUtil.toJsonStr(shelfLocation);
            log.info("recover 数据恢复：{}", param);
            mqttService.sendToMqtt(Constants.TOPIC_MQTT_RECOVER_TO_WCS, param);
            mqttRecordService.initMqttRecord(shelfLocation.getBoxCode(), Constants.TOPIC_MQTT_RECOVER_TO_WCS, 0, param);
        }
    }

    /**
     * 扩展用
     *
     * @param msg
     * @param topic
     * @return void
     * @author yw
     * @date 2022-04-03 11:42:12
     */
    public void extend(String msg, String topic) {

    }

    /**
     * wcs返回响应,发送至web
     *
     * @param msg
     * @return void
     * @author yw
     * @date 2022-03-20 09:36:00
     */
    @Transactional(rollbackFor = Exception.class)
    public void wcsToWmsResponse(String event, String msg, String key, boolean isClear) throws Exception {
        WcsResponse response = JSONUtil.toBean(msg, WcsResponse.class);
        UpOrDownPolicy upOrDownPolicy;
        upOrDownPolicy = upOrDownPolicyService.getOne(Wrappers.<UpOrDownPolicy>lambdaQuery().eq(UpOrDownPolicy::getBoxCode, response.getBoxCode()).in(UpOrDownPolicy::getType, Constants.BOX_OPERATION_UP, Constants.BOX_OPERATION_DOWN, Constants.BOX_OPERATION_WEIGHT));
        //异步有时未查询到数据，放缓存处理
        String policyKey = com.lvy.hczn.front.business.common.CacheConstants.WCS_POLICY + response.getBoxCode();
        if (upOrDownPolicy == null) {
            UpOrDownPolicy object = (UpOrDownPolicy) redisCache.get(policyKey);
            if (object != null) {
                log.error("redis查询：{}", JSON.toJSONString(object));
                upOrDownPolicy = object;
            }
        }
        redisCache.del(policyKey);
        if (upOrDownPolicy != null) {
            log.error("响应指令：{}", JSON.toJSONString(upOrDownPolicy));
            upOrDownPolicy.setResponse(true);
            MessageInfo messageInfo = new MessageInfo();
            Object object = redisCache.get(CacheConstants.SORT_TASK_SEAT_ID + upOrDownPolicy.getClientId() + upOrDownPolicy.getBoxCode());
            if (object != null) {
                messageInfo.setSeatId((Integer) object);
            }
            if (upOrDownPolicy.getProcessType() == Constants.STATE_PROCESS_TYPE_SEVEN) {
                /*if (response.getTotalWeight() != null && response.getTotalWeight().compareTo(BigDecimal.ZERO) > 0) {
                    initMessageInfo(upOrDownPolicy, MqConstants.SUCCESS, messageInfo);
                    //采购入库称重
                    messageInfo.setWarehousingEntryId(upOrDownPolicy.getWarehousingEntryId());
                    GoodsBox goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, upOrDownPolicy.getBoxCode()));
                    if (goodsBox != null) {
                        List<GoodsBoxDetail> detailList = goodsBoxDetailService.list(Wrappers.<GoodsBoxDetail>lambdaQuery().eq(GoodsBoxDetail::getBoxCode, goodsBox.getBoxCode()).gt(GoodsBoxDetail::getWeight, BigDecimal.ZERO));
                        if (MqConstants.ANSWER_ID_OK == response.getAnswerID()) {
                            //采购入库称重
                            warehousingEntrySortWeight(upOrDownPolicy, response, messageInfo, goodsBox, detailList);
                            UUID uuid = (UUID) redisCache.get(CacheConstants.REDIS_KEY_SOCKET_CLIENT + ":" + upOrDownPolicy.getClientId());
                            this.socketIo(uuid, "weighResp", messageInfo, upOrDownPolicy, ResultUtil.ok(messageInfo.getMsg()));
                        }
                    }
                    clearPolicy(upOrDownPolicy, upOrDownPolicy.getProcessState(), true);
                }*/
            } else {
                /**相应正常无需返回web,异常则返回web提示* */
                if (Constants.ANSWER_ID_OK == response.getAnswerID()) {

                    initMessageInfo(upOrDownPolicy, Constants.SUCCESS, messageInfo);

                    /**出库或者分拣，且时发送出库指令进入，1.设置货箱为出库中，2.若是分拣，则持续下架**/
                    if ((Constants.STATE_PROCESS_TYPE_ONE == upOrDownPolicy.getProcessType()
                            || (Constants.STATE_PROCESS_TYPE_ZERO == upOrDownPolicy.getProcessType())
                            && Constants.STATE_TO_WCS_TWO == upOrDownPolicy.getProcessState())) {
                        //出库或分拣响应成功，设置为出库中
                        LambdaUpdateWrapper<GoodsBox> updateWrapper = new LambdaUpdateWrapper<>();
                        updateWrapper.eq(GoodsBox::getBoxCode, upOrDownPolicy.getBoxCode()).set(GoodsBox::getStatus, Constants.GOODS_BOX_STATE_ONE);
                        goodsBoxService.update(updateWrapper);
                        if (Constants.STATE_PROCESS_TYPE_ONE == upOrDownPolicy.getProcessType()) {
                            //多仓出库
                            UpOrDownPolicy finalUpOrDownPolicy = upOrDownPolicy;
                            ThreadUtil.execAsync(() -> {
                                downloadMultipleWarehouse(finalUpOrDownPolicy);
                            });
                        }
                    }

                    //分拣到达缓存位后，货箱再发出库不入库指令，即分拣已下架的箱子，再指定出库，则直接删除指令,搬走箱子
                    //若是没在缓存位上，等到了缓存位流程再删
                    if (upOrDownPolicy.getProcessType().equals(Constants.STATE_PROCESS_TYPE_ZERO) && upOrDownPolicy.getProcessState() == Constants.STATE_TO_WCS_EIGHT) {
                        GoodsBox goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, upOrDownPolicy.getBoxCode()));
                        if (goodsBox != null) {
                            if (goodsBox.getStatus().equals(Constants.GOODS_BOX_STATE_TWO)) {
                                clearPolicy(upOrDownPolicy, upOrDownPolicy.getProcessState(), true);
                            }
                            //wcs消息队列响应成功，说明可以处理，即可置为空箱
                            goodsBox.setBoxType(Constants.GOODS_BOX_TYPE_ZERO);
                            log.error("goodsBoxService.updateById6");
                            goodsBoxService.updateById(goodsBox);
                            goodsBoxDetailService.delPhysicalByBoxCode(upOrDownPolicy.getBoxCode());

                            //删除采购入库时的缓存
                            String boxKey = CacheConstants.WCS_RESPONSE + upOrDownPolicy.getBoxCode() + ":" + Constants.STATE_PROCESS_TYPE_THREE;
                            redisCache.del(boxKey);

                            String keyInBufferPos = "InBufferPos" + Constants.TYPE_WCS_RESPONSE + upOrDownPolicy.getClientId();
                            List<MessageInfo> list = redisCache.getCacheList(keyInBufferPos);
                            if (list != null && !list.isEmpty()) {
//                                List<MessageInfo> listInBufferPos = list.stream().filter(i -> !(i.getBoxCode().equals(upOrDownPolicy.getBoxCode()))).collect(Collectors.toList());
                                LinkedHashMap<Object, Boolean> map1 = new LinkedHashMap<>();
                                UpOrDownPolicy finalUpOrDownPolicy1 = upOrDownPolicy;
                                List<Object> listInBufferPos = list.stream().filter(i -> map1.putIfAbsent(i.getBoxCode(), Boolean.TRUE) == null && !(i.getBoxCode().equals(finalUpOrDownPolicy1.getBoxCode()))).collect(Collectors.toList());
                                redisCache.del(keyInBufferPos);
                                redisCache.setAllListOfRight(keyInBufferPos, listInBufferPos);
                            }
                        }
                    }

                    /**货箱净重称重第二次响应，含有重量**/
                    if (response.getTotalWeight() != null) {
                        BigDecimal weight = response.getTotalWeight();
                        //后面加的，下面的set懒得注释
                        upOrDownPolicy.setTotalWeight(weight);
                        if (String.valueOf(upOrDownPolicy.getProcessType()).equals(Constants.GOODS_BOX_STATE_FIVE)) {
                            //称重毛重||货箱重量
                            //称重毛重有两次响应，第一次消息队列响应，表示能处理，第二次放到缓存位响应，返回称重信息
                            upOrDownPolicy.setTotalWeight(weight);
                            //上面已加1
                            upOrDownPolicy.setProcessState(upOrDownPolicy.getProcessState() - 1);
                            responseWeb(event, upOrDownPolicy, messageInfo, isClear, false, null);
                        }

                    }

                    if (!StrUtil.isEmpty(event)) {
                        //todo 做个测试web发数据后（后台延时返回web），然后web关闭，然后再打开，看数据发送情况，是否会发送，以什么方式发送
                        Object uuid = redisCache.get(CacheConstants.REDIS_KEY_SOCKET_CLIENT + ":" + upOrDownPolicy.getClientId());
                        this.socketIo(UUID.fromString(String.valueOf(uuid)), event, messageInfo, upOrDownPolicy, AjaxResult.success(messageInfo.getMsg()));
                    }
                    //todo 请求多次会自动+1，需过滤，如出库响应，按多次按钮，则会一直+1，而变成其他流程以及与mqtt断开重连的时候，会有重复记录，也会+1
                    //todo 不过方法为自动响应，无需人为操作，随已做了mqtt重复过滤，待观察再决定是否优化
                    //todo 出库响应，夹起货箱，会返回行列，确定这个货位为空，wms做更新处理
//                responseWeb(event, upOrDownPolicy, messageInfo, isClear, false, null);
                } else {
                    initMessageInfo(upOrDownPolicy, Constants.FAIL, messageInfo);
                    //wcs反馈异常，逻辑处理
                    dealWithError(event, response, upOrDownPolicy, messageInfo);
                }
            }

            mqttRecordService.initMqttRecord(response.getBoxCode(), "WCS_TO_WMS", 0, msg);
        } else {
            /*货箱入库，放到缓存位，自动发送指令*/
            if (com.lvy.hczn.front.business.common.Constants.ONE_ZERO_EIGHT.equals(response.getErrorID())) {
                wcsService.entryByPageType(response);
            }
            //批量出库响应，返回可下架箱子数量
        }
    }

    /**
     * 多仓循环出库，wcs队列20个，且需等wcs响应后才能再发下架，分拣下架过多的情况下，
     * 可能只会是一个仓库下架分拣，需循环仓库下架分级按，直到塞满队列
     *
     * @param upOrDownPolicy
     * @return void
     * @author yw
     * @date 2023-11-18 16:19:50
     */
    public void downloadMultipleWarehouse(UpOrDownPolicy upOrDownPolicy) {
        JSONArray array = (JSONArray) redisCache.get(CacheConstants.DICT_TYPE_WH_COM);
        if (array != null && !array.isEmpty()) {

            String clientIdInfo = upOrDownPolicy.getClientId();
            Integer clientIndex = null;
            /**获取上一个下架货箱的仓库index**/
            for (int i = 0; i < array.size(); i++) {
                SysDictData data = (SysDictData) array.get(i);
                if (data.getDictValue().equals(upOrDownPolicy.getClientId())) {
                    clientIndex = i;
                }
            }

            if (clientIndex == null) {
                return;
            }

            /**循环仓库，过滤上一个下架货箱的仓库，若是其他仓库没有货箱，则继续下架当前仓库货箱**/
            for (int i = 0; i < array.size(); i++) {
                SysDictData data = (SysDictData) array.get(i);
                if (!data.getDictValue().equals(clientIdInfo)) {
                    Long size = redisCache.getListSize(CacheConstants.SORT_TASK_DOWNLOAD + data.getDictValue());
                    if (size == null || size == 0) {
                        redisCache.del(CacheConstants.SORT_TASK_TAKE_NUM + data.getDictValue());
                    } else if (clientIndex > i && clientIndex < (array.size() - 1)) {
                        boolean empty = true;
                        /**当上一个下架仓库在中间时，比如有五个仓库，上一个在2号下架，则继续往下走，即，下架三号仓库的，若是三号仓库为空，
                         * 则继续往下,若是后面几个仓库，都为空，则下架当前循环的i仓库**/
                        for (int j = clientIndex + 1; j <= (array.size() - 1); j++) {
                            SysDictData sysDictData = (SysDictData) array.get(j);
                            Long sizeInfo = redisCache.getListSize(CacheConstants.SORT_TASK_DOWNLOAD + sysDictData.getDictValue());
                            if (sizeInfo == null || sizeInfo == 0) {
                                redisCache.del(CacheConstants.SORT_TASK_TAKE_NUM + sysDictData.getDictValue());
                            } else {
                                clientIdInfo = sysDictData.getDictValue();
                                empty = false;
                                break;
                            }
                        }
                        if (empty) {
                            clientIdInfo = data.getDictValue();
                        }
                        break;
                    } else {
                        clientIdInfo = data.getDictValue();
                        break;
                    }
                }
            }

            Param param = new Param();
            upOrDownPolicy.setClientId(clientIdInfo);
            param.setUpOrDownPolicy(upOrDownPolicy);
            sortingOutBatchInfo(upOrDownPolicy, "", param, false);
        }
    }

    /**
     * wcs反馈逻辑异常处理
     *
     * @param event
     * @param response
     * @param upOrDownPolicy
     * @param messageInfo
     * @return void
     * @author yw
     * @date 2022-07-05 21:29:42
     */
    public synchronized void dealWithError(String event, WcsResponse response, UpOrDownPolicy upOrDownPolicy, MessageInfo messageInfo) throws Exception {

        //todo 区分系统级异常以及web级异常，系统级异常，无需返回web端
        //21，22，23代表1，2，3号仓库分拣发送的货箱指令消息队列已满，若还有需要分拣的箱子，则到货箱已离开库位流程发送
        log.error("dealWithError异常：{}", JSONUtil.toJsonStr(response));
        if (Constants.checkOutboundErrorId(response.getErrorID())) {
            //todo 消息队列已满，把此货箱重新放入redis中
            //topic  WCS_TO_WMS 出库忙碌中异常且是分拣的响应异常，确定是分拣发送出库指令，wcs响应分拣出库忙碌中，重新放回redis
            if (upOrDownPolicy.getProcessType().equals(Constants.STATE_PROCESS_TYPE_ONE)) {
                log.error("下架消息队列已满：{}", JSONUtil.toJsonStr(upOrDownPolicy));
                wcsService.checkRedisExist(upOrDownPolicy);
//                redisCache.setListOfLeft(CacheConstants.SORT_TASK_DOWNLOAD + upOrDownPolicy.getClientId(), upOrDownPolicy.getBoxCode());
            }

            //wcs反馈的非系统级别的无法出入库异常，反馈回wcs后，清除
            upOrDownPolicy.setStatus(Constants.UP_OR_DOWN_POLICE_STATUS_ONE);
            upOrDownPolicy.setRemark(JSONUtil.toJsonStr(response));
            clearPolicy(upOrDownPolicy, upOrDownPolicy.getProcessState(), true);
        } else if (Constants.checkErrorId(response.getErrorID())) {
            //108消息队列无此数据，即：wms未发指令，货箱先放入缓存位，或者是随便放箱子，wsc都会进行扫码
            //web暂时不响应108，以及不响应的都放到这，108改成map key，或者在convert_data表把108设置成系统级别的，就不响应到web
            //操作人员看wcs操作屏
        } else {
            messageInfo.setCode(Constants.FAIL);
            //todo 根据ErrorID指定msg返回的异常信息
            //todo 异常响应后，wcs结束流程，wms结束流程
            // 则执行中指令表的对应的boxCode的数据需删除，且标志为异常流程数据到历史表
            // ,然后重新走流程，生成新的记录；
            // todo 考虑web端断开情况，数据不能丢失即发到web
            //  端，然后web端断开了，重连后，此数据没有了，最好存到redis,然后web端收到
            //  后SocketIoHandler class下的done方法，做个删除，所有，web端重连的时
            //  候做个redis查询掉之后wcs返回的数据,web得响应完成后才能处理下一步

            /**wcs异常转码**/
            String convertCodeMsg = (String) redisCache.get(CacheConstants.CONVERT_CODE + response.getAnswerID() + ":" + response.getErrorID());
            if (StrUtil.isNotEmpty(convertCodeMsg)) {
                messageInfo.setMsg(convertCodeMsg + "(" + upOrDownPolicy.getBoxCode() + ")");
            } else {
                messageInfo.setMsg(String.valueOf(response.getErrorID()));
            }

            //入库&&分拣称重异常
            GoodsBox goodsBox;
            String boxKey = CacheConstants.WCS_RESPONSE + upOrDownPolicy.getBoxCode() + ":" + upOrDownPolicy.getProcessType();
            Param paramInfo = redisCache.getCacheObject(boxKey);
            if (paramInfo == null || paramInfo.getGoodsBox() == null) {
                goodsBox = goodsBoxService.getOne(Wrappers.<GoodsBox>lambdaQuery().eq(GoodsBox::getBoxCode, upOrDownPolicy.getBoxCode()));
            } else {
                goodsBox = paramInfo.getGoodsBox();
            }
            if (response.getTotalWeight() != null) {
                if (goodsBox != null) {
//                    goodsBox.setBoxType(MqConstants.GOODS_BOX_TYPE_ZERO);
//                    goodsBoxService.updateById(goodsBox);
//                    goodsBoxDetailService.delPhysicalByBoxCode(goodsBox.getBoxCode());
                    //当总重量小于货箱重量（误差），则认为时空箱，总重量为0
                    BigDecimal weight = response.getTotalWeight().subtract(goodsBox.getBoxStandardWeight());
                    if (weight.compareTo(BigDecimal.ZERO) > 0) {
                        if (upOrDownPolicy.getSortWeight() != null) {
                            BigDecimal decimal = goodsBox.getWeight().subtract(upOrDownPolicy.getSortWeight()).setScale(2, RoundingMode.HALF_UP);
                            if (decimal.compareTo(BigDecimal.ZERO) > 0) {
                                messageInfo.setActWeight(decimal);
                            } else {
                                messageInfo.setActWeight(BigDecimal.ZERO);
                            }

                        } else {
                            messageInfo.setActWeight(goodsBox.getWeight());
                        }
                        messageInfo.setTotalWeight(weight.divide(BigDecimal.valueOf(Constants.WCS_WEIGHT_UNIT_COEFFICIENT)).setScale(2, RoundingMode.HALF_UP));
                    } else {
                        messageInfo.setTotalWeight(BigDecimal.ZERO);
                        messageInfo.setActWeight(BigDecimal.ZERO);
                    }

                } else {
                    messageInfo.setTotalWeight(response.getTotalWeight());
                }
                messageInfo.setData(response);
            }

            //记录异常代码
//            upOrDownPolicy.setRemark(String.valueOf(response.getErrorID()));
//            clearPolicy(upOrDownPolicy, MqConstants.STATE_TO_WCS_ZERO, false);
            //todo 可根据异常ID过滤非必要的报警信息，如:wcs消息队列满了的异常
            if (Constants.STATE_PROCESS_TYPE_ONE == upOrDownPolicy.getProcessType() && upOrDownPolicy.getType().equals(Constants.BOX_OPERATION_DOWN)) {
                //分拣下架响应失败
                //todo 分拣任务为批量发，失败时考虑异步重发，或者定时重发
                //todo 入库考虑定时重发，则到时考虑指令表状态为重发状态，定时扫描指令表重发是否可行
                //todo 定时重发的话，更改upOrDownPolicy指令状态，然后删除redis
            } else if (Constants.STATE_PROCESS_TYPE_THREE == upOrDownPolicy.getProcessType() && Constants.STATE_TO_WCS_TWELVE == upOrDownPolicy.getProcessState()) {
                String key = CacheConstants.WCS_RESPONSE + upOrDownPolicy.getBoxCode() + ":" + upOrDownPolicy.getProcessType();
                redisCache.del(key);
            }
//            messageInfo.setData(response);

            //wcs反馈的非系统级别的无法出入库异常，反馈回wcs后，清除
            upOrDownPolicy.setStatus(Constants.UP_OR_DOWN_POLICE_STATUS_ONE);
            upOrDownPolicy.setRemark(JSONUtil.toJsonStr(response));
            clearPolicy(upOrDownPolicy, upOrDownPolicy.getProcessState(), true);
            /**
             * 响应返回到web
             */
            Object object = redisCache.get(CacheConstants.REDIS_KEY_SOCKET_CLIENT + ":" + upOrDownPolicy.getClientId());
            this.socketIo(UUID.fromString(String.valueOf(object)), event, messageInfo, upOrDownPolicy, AjaxResult.error(messageInfo.getMsg()));
        }
    }

    /**
     * 返回web，返回的是正常或者异常，然后web页面再去获取redis存储的wcs的返回信息
     * 考量：直接返回，在web浏览器断开后，数据无法返回到web
     *
     * @param uuid
     * @param event
     * @param messageInfo
     * @param upOrDownPolicy
     * @param resultUtil
     * @return void
     * @author yw
     * @date 2022-05-20 15:39:15
     */
    public synchronized void socketIo(UUID uuid, String event, MessageInfo messageInfo, UpOrDownPolicy upOrDownPolicy, AjaxResult resultUtil) {
        SocketIOClient socketIOClient = socketIOServer.getClient(uuid);

        log.error("响应到web:{},{},{}", uuid, event, JSON.toJSONString(messageInfo));
        //做个缓存，web接收到后，触发删除
//        if (!upOrDownPolicy.getProcessType().equals(MqConstants.STATE_PROCESS_TYPE_ZERO) && upOrDownPolicy.getProcessState() != MqConstants.STATE_TO_WCS_SEVEN) {
        redisCache.setListOfRight(event + Constants.TYPE_WCS_RESPONSE + upOrDownPolicy.getClientId(), messageInfo);
//        }

        if (socketIOClient != null) {
            socketIOClient.sendEvent(event, JSONUtil.toJsonStr(resultUtil));
        } else {
            log.error("响应到web异常，socketIOClient为空");
        }
    }

    /**
     * 清除指令,放入历史表
     *
     * @param upOrDownPolicy
     * @param process
     * @return void
     * @author yw
     * @date 2022-03-26 11:06:39
     */
    public int clearPolicy(UpOrDownPolicy upOrDownPolicy, Integer process, boolean isClear) throws Exception {
        //如果是wcs按按钮，返回的异常，不处理
        if (!upOrDownPolicy.isUpByButton()) {
            if (isClear) {
                /**
                 * 响应之后就结束的流程，清除数据，变成历史数据
                 */
                UpOrDownPolicyHistory history = JSONUtil.toBean(JSONUtil.toJsonStr(upOrDownPolicy), UpOrDownPolicyHistory.class);
                history.setProcessState(process);
                history.setCreateTime(LocalDateTime.now());
                history.setUpdateTime(LocalDateTime.now());
                UpOrDownPolicyHistory tmp = upOrDownPolicyHistoryService.getById(history.getId());
                //做个校验，防止mqtt重发
                if (tmp != null) {
                    upOrDownPolicyHistoryService.updateById(history);
                } else {
                    upOrDownPolicyHistoryService.save(history);
                }
                log.error("clearPolicy:{},{}", upOrDownPolicy.getType(), upOrDownPolicy.getBoxCode());
                return upOrDownPolicyService.delPhysicalById(upOrDownPolicy.getId());
            } else {
                upOrDownPolicy.setProcessState(process);
                upOrDownPolicyService.updateById(upOrDownPolicy);
                return 1;
            }
        }
        return 0;
    }
}
